From bade06774fa551b0ead01010f22d9aff308f82b5 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Tue, 20 Oct 2026 15:17:17 +0000 Subject: [PATCH] chore: trim unused tooling and example chrome Drop scripts and CI packaging nothing calls, correct the license and changelog links, and bring catalog examples in line with the terminal component language. Co-authored-by: Leo Farias --- .fvmrc | 3 - .github/workflows/publish.yml | 14 -- GOALS.md | 2 +- README.md | 2 +- packages/noir/analysis_options.yaml | 6 - packages/noir/example/analysis_options.yaml | 6 + packages/noir/example/autocomplete_demo.dart | 1 - packages/noir/example/components_demo.dart | 2 - packages/noir/example/data_table_demo.dart | 2 - packages/noir/example/dialog_demo.dart | 1 - packages/noir/example/file_picker_demo.dart | 55 ++--- .../noir/example/framework_primitives.dart | 83 ++++---- packages/noir/example/image_demo.dart | 53 ++--- packages/noir/example/listview_demo.dart | 2 - .../noir/example/parity_components_demo.dart | 2 - packages/noir/example/pub_search.dart | 1 - packages/noir/example/pulse_animation.dart | 3 +- packages/noir/example/scrollbox_demo.dart | 2 - packages/noir/example/select_demo.dart | 2 - packages/noir/example/src/chat/app.dart | 23 +- .../noir/example/src/pub_search/models.dart | 65 ------ .../example/src/shared/demo_scaffold.dart | 15 +- packages/noir/example/textarea_demo.dart | 2 - packages/noir/example/theme_demo.dart | 2 - packages/noir/example/widgets_tour.dart | 2 - .../package_distribution_ownership_test.dart | 1 - .../example/framework_primitives_test.dart | 2 +- .../test/goldens/chat_demo_64x18.buffer.txt | 8 +- .../test/goldens/chat_demo_64x18.styles.txt | 21 +- .../test/tools/mcp_inspector_drive_test.dart | 2 +- packages/noir/tool/analysis_options.yaml | 6 + packages/noir/tool/mcp_fixtures/README.md | 2 + packages/noir/tool/mcp_inspector/FINDINGS.md | 141 +------------ packages/noir/tool/mcp_inspector/README.md | 15 +- .../tool/mcp_inspector/bin/mcp_inspector.dart | 2 +- .../mcp_inspector/test/compiled_fixture.dart | 2 +- packages/noir/tool/screenshot_iterm.sh | 17 -- packages/noir/tool/snapshot_scenes.dart | 199 ------------------ 38 files changed, 144 insertions(+), 625 deletions(-) delete mode 100644 .fvmrc create mode 100644 packages/noir/example/analysis_options.yaml create mode 100644 packages/noir/tool/analysis_options.yaml delete mode 100755 packages/noir/tool/screenshot_iterm.sh delete mode 100644 packages/noir/tool/snapshot_scenes.dart diff --git a/.fvmrc b/.fvmrc deleted file mode 100644 index c300356c..00000000 --- a/.fvmrc +++ /dev/null @@ -1,3 +0,0 @@ -{ - "flutter": "stable" -} \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cc058042..66bf2fa8 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -133,20 +133,6 @@ jobs: run: dart pub publish --dry-run working-directory: ${{ steps.tag.outputs.directory }} - # Only Noir carries bundled native binaries. They are committed, not - # built, so this is a repackaging — but the next job proves the packaged - # copy actually loads, and it does so while the upload can still be - # stopped. - - name: Package tracked native assets - if: >- - steps.preflight.outputs.publish == 'true' - && steps.tag.outputs.package == 'noir' - timeout-minutes: 2 - shell: bash - run: tar -czf "opentui-binaries-$RELEASE_TAG.tar.gz" native native_manifest.json LICENSE THIRD_PARTY_NOTICES.md third_party - env: - RELEASE_TAG: ${{ github.ref_name }} - - name: Upload package-test assets if: >- steps.preflight.outputs.publish == 'true' diff --git a/GOALS.md b/GOALS.md index 0ff113a5..9a80205b 100644 --- a/GOALS.md +++ b/GOALS.md @@ -1,7 +1,7 @@ # Noir quality standard This document is the durable standard for Noir. The current candidate is -described at the top of [`CHANGELOG.md`](CHANGELOG.md); open work lives in +described at the top of [`CHANGELOG.md`](packages/noir/CHANGELOG.md); open work lives in GitHub issues and pull requests. ## Architectural invariant diff --git a/README.md b/README.md index 7d07f61c..3ee76df0 100644 --- a/README.md +++ b/README.md @@ -34,5 +34,5 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for the required checks and ## License -MIT. See [LICENSE](LICENSE) and +BSD 3-Clause. See [LICENSE](LICENSE) and [the third-party notices](packages/noir/THIRD_PARTY_NOTICES.md). diff --git a/packages/noir/analysis_options.yaml b/packages/noir/analysis_options.yaml index ae0caf5e..62018271 100644 --- a/packages/noir/analysis_options.yaml +++ b/packages/noir/analysis_options.yaml @@ -18,12 +18,6 @@ analyzer: - test/fixtures/source_package_consumer/** - tool/mcp_fixtures/** - tool/mcp_inspector/** - - android/** - - ios/** - - web/** - - windows/** - - macos/** - - linux/** linter: rules: diff --git a/packages/noir/example/analysis_options.yaml b/packages/noir/example/analysis_options.yaml new file mode 100644 index 00000000..4e523775 --- /dev/null +++ b/packages/noir/example/analysis_options.yaml @@ -0,0 +1,6 @@ +# Examples are not the published API. Keep every other package lint. +include: ../analysis_options.yaml + +linter: + rules: + public_member_api_docs: false diff --git a/packages/noir/example/autocomplete_demo.dart b/packages/noir/example/autocomplete_demo.dart index 72a5b8ce..2c7b09d3 100644 --- a/packages/noir/example/autocomplete_demo.dart +++ b/packages/noir/example/autocomplete_demo.dart @@ -1,4 +1,3 @@ -// Run with: dart run example/autocomplete_demo.dart // The initial `noi` query demonstrates application-owned debounce and stale // response protection. Type to replace or refine it, Tab into suggestions, // use arrows to move, Enter or click to choose, and Escape to clear results or diff --git a/packages/noir/example/components_demo.dart b/packages/noir/example/components_demo.dart index 4e1dfda3..817bdd39 100644 --- a/packages/noir/example/components_demo.dart +++ b/packages/noir/example/components_demo.dart @@ -1,5 +1,3 @@ -// Run with: dart run example/components_demo.dart -// // Tab and Shift+Tab move between the interactive controls; Space or Enter // activates the focused one, and a left click does the same. Press Ctrl+T to // swap the complete palette, Ctrl+S to stop or restart the spinner, and Ctrl+Q diff --git a/packages/noir/example/data_table_demo.dart b/packages/noir/example/data_table_demo.dart index c10b18d3..957786f2 100644 --- a/packages/noir/example/data_table_demo.dart +++ b/packages/noir/example/data_table_demo.dart @@ -1,5 +1,3 @@ -// Run with: dart run example/data_table_demo.dart -// // Use ↑/↓ (or j/k), PageUp/PageDown, Home/End to move the highlight and Enter // to open a row. Press s to cycle sortable columns, or click a `package` or // `state` header; repeating either action reverses the direction. Press q to diff --git a/packages/noir/example/dialog_demo.dart b/packages/noir/example/dialog_demo.dart index e9106069..065b48e1 100644 --- a/packages/noir/example/dialog_demo.dart +++ b/packages/noir/example/dialog_demo.dart @@ -1,4 +1,3 @@ -// Run with: dart run example/dialog_demo.dart // Activate `Review deploy` to open the dialog. Tab and Shift+Tab remain inside // it; Escape or Cancel closes it, and Deploy confirms. The full-screen pointer // barrier keeps the underlying screen visible without letting it receive diff --git a/packages/noir/example/file_picker_demo.dart b/packages/noir/example/file_picker_demo.dart index 5e2501e5..36a3c270 100644 --- a/packages/noir/example/file_picker_demo.dart +++ b/packages/noir/example/file_picker_demo.dart @@ -1,4 +1,3 @@ -// Run with: dart run example/file_picker_demo.dart // The example uses an in-memory project tree and never reads the host file // system. Up/Down move, Right expands, Left collapses or selects the parent, // and Enter toggles a folder. Tab reaches Cancel and Open; Escape closes the @@ -216,50 +215,34 @@ class _FilePickerDemoAppState extends State { crossAxisAlignment: CrossAxisAlignment.stretch, children: [ SizedBox( - height: 11, + height: 12, child: Row( + spacing: 1, children: [ - SizedBox( - width: 32, - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - Text( - _treeFocus.hasFocus ? '› Project' : 'Project', - style: TextStyle( - color: _treeFocus.hasFocus - ? theme.accent - : theme.textMuted, - fontWeight: _treeFocus.hasFocus - ? FontWeight.bold - : FontWeight.normal, - ), - ), - TreeView( - key: const ValueKey('file-tree'), - controller: _tree, - itemBuilder: _buildTreeRow, - height: 10, - focusNode: _treeFocus, - showScrollIndicator: true, - backgroundColor: Color.transparent, - onActivate: _openNode, - ), - ], + Panel( + title: 'Project', + focused: _treeFocus.hasFocus, + width: 34, + height: 12, + child: TreeView( + key: const ValueKey('file-tree'), + controller: _tree, + itemBuilder: _buildTreeRow, + height: 10, + focusNode: _treeFocus, + showScrollIndicator: true, + backgroundColor: Color.transparent, + onActivate: _openNode, ), ), - const SizedBox(height: 11, child: Divider(axis: Axis.vertical)), Expanded( - child: Container( - padding: const EdgeInsets.only(left: 1), + child: Panel( + title: 'Preview', + height: 12, child: Column( spacing: 1, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - Text( - 'Preview', - style: TextStyle(color: theme.textMuted), - ), Text( selectedPath ?? 'No selection', maxLines: 1, diff --git a/packages/noir/example/framework_primitives.dart b/packages/noir/example/framework_primitives.dart index 9b560744..f1080551 100644 --- a/packages/noir/example/framework_primitives.dart +++ b/packages/noir/example/framework_primitives.dart @@ -1,5 +1,3 @@ -// Run with: dart run example/framework_primitives.dart -// // Press Enter or Space, or click Activate, to increment. Press q to quit. import 'package:noir/noir.dart'; @@ -56,51 +54,54 @@ class _FrameworkPrimitivesAppState extends State { } @override - Widget build(BuildContext context) => Shortcuts( - shortcuts: const { - SingleActivator(LogicalKeyboardKey.enter): _IncrementIntent(), - SingleActivator(LogicalKeyboardKey.space): _IncrementIntent(), - }, - child: Actions( - actions: >{ - _IncrementIntent: CallbackAction<_IncrementIntent>((intent, context) { - _activationKey.currentState?.activate('keyboard'); - return KeyEventResult.handled; - }), + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Shortcuts( + shortcuts: const { + SingleActivator(LogicalKeyboardKey.enter): _IncrementIntent(), + SingleActivator(LogicalKeyboardKey.space): _IncrementIntent(), }, - child: Focus( - autofocus: true, - onKeyEvent: _handleQuit, - child: DemoScaffold( - title: 'Framework primitives', - hint: 'Enter/Space/click increments. q quits.', - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 1, - children: [ - RichText( - text: TextSpan( - text: 'Count: ', - style: const TextStyle(color: Color.lightGray), - children: [ - TextSpan( - text: '${_count.value}', - style: const TextStyle( - color: Color.yellow, - fontWeight: FontWeight.bold, + child: Actions( + actions: >{ + _IncrementIntent: CallbackAction<_IncrementIntent>((intent, context) { + _activationKey.currentState?.activate('keyboard'); + return KeyEventResult.handled; + }), + }, + child: Focus( + autofocus: true, + onKeyEvent: _handleQuit, + child: DemoScaffold( + title: 'Framework primitives', + hint: 'Enter/Space/click increments. q quits.', + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 1, + children: [ + RichText( + text: TextSpan( + text: 'Count: ', + style: TextStyle(color: theme.textMuted), + children: [ + TextSpan( + text: '${_count.value}', + style: TextStyle( + color: theme.text, + fontWeight: FontWeight.bold, + ), ), - ), - ], + ], + ), ), - ), - _ActivationSurface(key: _activationKey, onActivate: _activate), - Text('Last activation: $_lastActivation'), - ], + _ActivationSurface(key: _activationKey, onActivate: _activate), + Text('Last activation: $_lastActivation'), + ], + ), ), ), ), - ), - ); + ); + } } class _ActivationSurface extends StatefulWidget { diff --git a/packages/noir/example/image_demo.dart b/packages/noir/example/image_demo.dart index 637db218..be6076e9 100644 --- a/packages/noir/example/image_demo.dart +++ b/packages/noir/example/image_demo.dart @@ -27,29 +27,32 @@ class ImageDemo extends StatelessWidget { ]); @override - Widget build(BuildContext context) => Container( - color: Color.black, - padding: const EdgeInsets.all(1), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 1, - children: [ - const Text( - 'Embedded RGBA image', - style: TextStyle(color: Color.yellow, fontWeight: FontWeight.bold), - ), - Image.rgba( - _checkerboard, - pixelWidth: 2, - pixelHeight: 2, - rowStride: 8, - width: 12, - height: 6, - fit: ImageFit.fill, - ), - const Text('Ctrl+C exits.', style: TextStyle(color: Color.lightGray)), - ], - ), - ); + Widget build(BuildContext context) { + final theme = Theme.of(context); + return Container( + color: theme.surface, + padding: const EdgeInsets.all(1), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 1, + children: [ + Text( + 'Embedded RGBA image', + style: TextStyle(color: theme.text, fontWeight: FontWeight.bold), + ), + Image.rgba( + _checkerboard, + pixelWidth: 2, + pixelHeight: 2, + rowStride: 8, + width: 12, + height: 6, + fit: ImageFit.fill, + ), + Text('Ctrl+C exits.', style: TextStyle(color: theme.textMuted)), + ], + ), + ); + } } diff --git a/packages/noir/example/listview_demo.dart b/packages/noir/example/listview_demo.dart index 17fb0939..d3b7d546 100644 --- a/packages/noir/example/listview_demo.dart +++ b/packages/noir/example/listview_demo.dart @@ -1,5 +1,3 @@ -// Run with: dart run example/listview_demo.dart -// // Tab moves between the two lists. In the selectable list use ↑/↓ (or j/k), // PageUp/PageDown, Home/End, and Enter; the plain list scrolls with the same // keys and with the mouse wheel. Press q to quit. diff --git a/packages/noir/example/parity_components_demo.dart b/packages/noir/example/parity_components_demo.dart index 8539051e..d6bd2d21 100644 --- a/packages/noir/example/parity_components_demo.dart +++ b/packages/noir/example/parity_components_demo.dart @@ -1,5 +1,3 @@ -// Run with: dart run example/parity_components_demo.dart -// // Left/Right or [/] changes the active document tab. Arrow keys adjust the // slider when it has focus. Document text supports pointer/Shift selection, // Ctrl+A, Escape, and explicit Ctrl+C OSC52 copy. Press q to quit. diff --git a/packages/noir/example/pub_search.dart b/packages/noir/example/pub_search.dart index 1b1bfc1b..4df351b2 100644 --- a/packages/noir/example/pub_search.dart +++ b/packages/noir/example/pub_search.dart @@ -1,4 +1,3 @@ -// Run with: dart run example/pub_search.dart // Requires network access. Type at least three characters for completion; // Enter searches or opens the selected package. Tab crosses the query, sort, // filter, and results. Sort and filter open with Enter, Space, or a click. From diff --git a/packages/noir/example/pulse_animation.dart b/packages/noir/example/pulse_animation.dart index 78225a48..8913b7d3 100644 --- a/packages/noir/example/pulse_animation.dart +++ b/packages/noir/example/pulse_animation.dart @@ -78,10 +78,9 @@ class _PulseAnimationDemoState extends State onKeyEvent: _onKey, child: Container( color: theme.surface, - alignment: Alignment.center, padding: EdgeInsets.all(2), child: Column( - mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, spacing: 1, children: [ Text( diff --git a/packages/noir/example/scrollbox_demo.dart b/packages/noir/example/scrollbox_demo.dart index 0f2372d9..85678afd 100644 --- a/packages/noir/example/scrollbox_demo.dart +++ b/packages/noir/example/scrollbox_demo.dart @@ -1,5 +1,3 @@ -// Run with: dart run example/scrollbox_demo.dart -// // Use ↑/↓ to scroll one line, PgUp/PgDn for a page, Home/End for ends. // Press q to quit. diff --git a/packages/noir/example/select_demo.dart b/packages/noir/example/select_demo.dart index ba79faa6..50f5ef35 100644 --- a/packages/noir/example/select_demo.dart +++ b/packages/noir/example/select_demo.dart @@ -1,5 +1,3 @@ -// Run with: dart run example/select_demo.dart -// // Use ↑/↓ (or j/k) to move the highlight, Enter to confirm. Press q to quit. // Demonstrates Select rendering, focus, keyboard navigation, and the // onChanged vs onSelect callback split. diff --git a/packages/noir/example/src/chat/app.dart b/packages/noir/example/src/chat/app.dart index 932ddae2..15697183 100644 --- a/packages/noir/example/src/chat/app.dart +++ b/packages/noir/example/src/chat/app.dart @@ -1408,22 +1408,13 @@ class _ChatDemoAppState extends State { ); } - Widget _buildModal(BuildContext context) { - final content = switch (_surface) { - _ModalSurface.permission => _buildPermissionModal(context), - _ModalSurface.question => _buildQuestionModal(context), - _ModalSurface.model => _buildModelModal(context), - _ModalSurface.sessions => _buildSessionModal(context), - null => const SizedBox(), - }; - return Container( - color: Theme.of(context).surface, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [content], - ), - ); - } + Widget _buildModal(BuildContext context) => switch (_surface) { + _ModalSurface.permission => _buildPermissionModal(context), + _ModalSurface.question => _buildQuestionModal(context), + _ModalSurface.model => _buildModelModal(context), + _ModalSurface.sessions => _buildSessionModal(context), + null => const SizedBox(), + }; Widget _buildPermissionModal(BuildContext context) { final theme = Theme.of(context); diff --git a/packages/noir/example/src/pub_search/models.dart b/packages/noir/example/src/pub_search/models.dart index 9ec4ccaa..e2d24961 100644 --- a/packages/noir/example/src/pub_search/models.dart +++ b/packages/noir/example/src/pub_search/models.dart @@ -53,7 +53,6 @@ enum PubSuggestionKind { /// One completion row. Package rows are names only; topic rows carry a count. final class PubSuggestion { - /// A package-name suggestion. const PubSuggestion.package(this.name) : kind = PubSuggestionKind.package, packageCount = null; @@ -62,10 +61,8 @@ final class PubSuggestion { const PubSuggestion.topic(this.name, this.packageCount) : kind = PubSuggestionKind.topic; - /// Package or topic. final PubSuggestionKind kind; - /// Package name or topic slug. final String name; /// Package count for a topic; null for a package name. @@ -78,7 +75,6 @@ final class PubSuggestion { /// application already owns both, so repeating them here would create a second /// source of truth that could disagree with the controls on screen. final class PackageSearchPage { - /// Creates a result page. PackageSearchPage({ required this.page, required List packages, @@ -89,7 +85,6 @@ final class PackageSearchPage { /// One-based result page. final int page; - /// Package names returned by pub.dev. final List packages; /// Whether pub.dev supplied a next-page URL. @@ -101,7 +96,6 @@ final class PackageSearchPage { /// Documentation and release information for one published version. final class PackageRelease { - /// Creates a release summary. const PackageRelease({ required this.version, required this.published, @@ -110,13 +104,11 @@ final class PackageRelease { this.documentationStatus, }); - /// Semantic version string. final String version; /// Publication time reported by pub.dev. final DateTime published; - /// Whether this release was retracted. final bool retracted; /// Whether hosted API documentation exists, when known. @@ -128,7 +120,6 @@ final class PackageRelease { /// A security advisory reduced to terminal-friendly fields. final class PackageAdvisorySummary { - /// Creates an advisory summary. PackageAdvisorySummary({ required this.id, this.summary, @@ -137,7 +128,6 @@ final class PackageAdvisorySummary { List affectedVersions = const [], }) : affectedVersions = List.unmodifiable(affectedVersions); - /// Advisory identifier. final String id; /// Short advisory title from OSV. @@ -156,7 +146,6 @@ final class PackageAdvisorySummary { /// One section from the package analyzer report. final class PackageHealthSection { - /// Creates an analyzer section summary. const PackageHealthSection({ required this.title, required this.status, @@ -165,31 +154,25 @@ final class PackageHealthSection { this.maxPoints, }); - /// Human-readable section title. final String title; - /// Analyzer status. final String status; /// GitHub-flavoured markdown from pana's `ReportSection.summary`. final String summary; - /// Points granted for this section. final int? grantedPoints; - /// Maximum points for this section. final int? maxPoints; } /// A screenshot entry from the package pubspec. final class PackageScreenshotSummary { - /// Creates a screenshot summary. const PackageScreenshotSummary({ required this.description, required this.path, }); - /// Screenshot description. final String description; /// Package-relative screenshot path. @@ -198,7 +181,6 @@ final class PackageScreenshotSummary { /// Download counts attributed to one semantic-version range. final class PackageVersionDownloads { - /// Creates a version-range history. PackageVersionDownloads({ required this.versionRange, required List counts, @@ -228,7 +210,6 @@ enum PackageDependencySource { /// Structured, terminal-friendly dependency metadata from a package pubspec. final class PackageDependencySummary { - /// Creates a dependency summary. const PackageDependencySummary({ required this.source, this.constraint, @@ -239,7 +220,6 @@ final class PackageDependencySummary { this.sdk, }); - /// Dependency source kind. final PackageDependencySource source; /// Hosted or SDK version constraint. @@ -251,13 +231,11 @@ final class PackageDependencySummary { /// Hosted repository or Git URL. final String? url; - /// Git ref. final String? ref; /// Git subpath or local package path. final String? path; - /// SDK name. final String? sdk; /// Complete compact representation for the Dependencies tab. @@ -287,7 +265,6 @@ final class PackageDependencySummary { /// Repository metadata inferred by pana. final class PackageRepositorySummary { - /// Creates a repository summary. const PackageRepositorySummary({ this.provider, this.host, @@ -298,7 +275,6 @@ final class PackageRepositorySummary { /// Source host provider, such as GitHub. final String? provider; - /// Repository host. final String? host; /// Owner and repository name. @@ -568,52 +544,37 @@ final class PubPackageSnapshot { ); } - /// Package name. final String name; - /// Latest version. final String version; - /// Latest pubspec description. final String description; - /// Latest publication time. final DateTime published; - /// Canonical pub.dev package page. final String packageUrl; - /// Canonical pub.dev changelog page. final String changelogUrl; - /// Whether the latest release is retracted. final bool retracted; - /// Verified publisher identifier. final String? publisher; - /// Pubspec environment constraints. final Map environment; - /// Homepage URL. final String? homepage; - /// Repository URL. final String? repository; - /// Issue tracker URL. final String? issueTracker; - /// Documentation URL. final String? documentationUrl; /// Contributing guide URL inferred by pana. final String? contributingUrl; - /// Funding URLs. final List fundingUrls; - /// Pubspec topics. final List topics; /// Supported platforms derived from score tags. @@ -625,49 +586,36 @@ final class PubPackageSnapshot { /// License identifiers and paths. final List licenses; - /// Pubspec publication target. final String? publishTo; - /// Pubspec screenshots. final List screenshots; - /// Direct runtime dependencies. final Map directDependencies; - /// Direct development dependencies. final Map devDependencies; - /// Pubspec dependency overrides. final Map dependencyOverrides; /// All dependencies observed by pana. final List transitiveDependencies; - /// Package executables. final Map executables; - /// Workspace members. final List workspace; - /// Pub workspace resolution mode. final String? resolution; - /// Granted pub points. final int? grantedPoints; - /// Maximum pub points. final int? maxPoints; - /// Like count. final int? likeCount; /// Downloads in the latest 30-day window. final int? downloadCount30Days; - /// Published versions. final List releases; - /// Whether the package is discontinued. final bool isDiscontinued; /// Replacement package, when discontinued. @@ -676,22 +624,16 @@ final class PubPackageSnapshot { /// Whether the package is hidden from search. final bool isUnlisted; - /// Security advisories. final List advisories; - /// Time the advisory set was refreshed. final DateTime? advisoriesUpdated; - /// Pana report status. final String? analysisStatus; - /// Dartdoc report status. final String? dartdocStatus; - /// Scorecard task status. final String? taskStatus; - /// Individual pana report sections. final List healthSections; /// URL problems reported by pana. @@ -700,19 +642,15 @@ final class PubPackageSnapshot { /// Screenshot checks reported by pana. final List analysisScreenshots; - /// Scorecard update time. final DateTime? metricsUpdated; - /// Pana analysis time. final DateTime? analysisUpdated; /// Package version analyzed by the scorecard. final String? scorecardPackageVersion; - /// Scorecard runtime version. final String? scorecardRuntimeVersion; - /// Pana version. final String? panaVersion; /// SDK version used for analysis. @@ -730,13 +668,10 @@ final class PubPackageSnapshot { /// Pana result maximum points. final int? analysisMaxPoints; - /// Total weekly download history. final List weeklyDownloads; - /// Weekly downloads grouped by major-version ranges. final List majorVersionDownloads; - /// Date of the newest weekly count. final DateTime? weeklyDownloadsNewestDate; } diff --git a/packages/noir/example/src/shared/demo_scaffold.dart b/packages/noir/example/src/shared/demo_scaffold.dart index 5cee5450..f7e08c94 100644 --- a/packages/noir/example/src/shared/demo_scaffold.dart +++ b/packages/noir/example/src/shared/demo_scaffold.dart @@ -1,17 +1,4 @@ -/// Shared chrome for the Noir example apps. -/// -/// Every demo used to hand-roll the same padded surface with a bold title and -/// muted hint. This file keeps that application-specific page frame in one -/// place; reusable bordered regions use Noir's public [Panel]. All chrome -/// colors come from `Theme.of(context)`, so the demos render on -/// `ThemeData.dark` by default and re-skin together under any `Theme`. -/// -/// The card is content-sized vertically. Horizontal room comes from the -/// scaffold owning the terminal (`alignment: topLeft`) and a 2-cell side -/// inset so the page title is not glued to the edge. -/// -/// Content colors (chat speakers, layout blocks, particles) stay in each demo: -/// there the color is the subject, not the frame. +/// Example-local page chrome, with colors from `Theme.of`. library; import 'package:noir/noir.dart'; diff --git a/packages/noir/example/textarea_demo.dart b/packages/noir/example/textarea_demo.dart index 712a7d4d..6e5477e5 100644 --- a/packages/noir/example/textarea_demo.dart +++ b/packages/noir/example/textarea_demo.dart @@ -1,5 +1,3 @@ -// Run with: dart run example/textarea_demo.dart -// // Type to insert text. Enter inserts a newline. Ctrl+D submits. Ctrl+Enter // also submits when the terminal reports modified Enter keys. // Backspace, arrows, Home/End, PgUp/PgDn, Ctrl+Home/End all work. diff --git a/packages/noir/example/theme_demo.dart b/packages/noir/example/theme_demo.dart index f10d981a..42fa2904 100644 --- a/packages/noir/example/theme_demo.dart +++ b/packages/noir/example/theme_demo.dart @@ -1,5 +1,3 @@ -// Run with: dart run example/theme_demo.dart -// // Press t to swap palettes, q to quit. Demonstrates Theme/ThemeData: one // InheritedWidget publishes a flat token set, and every widget below it // resolves its colors from the nearest enclosing Theme. diff --git a/packages/noir/example/widgets_tour.dart b/packages/noir/example/widgets_tour.dart index a3974ae7..537bc20b 100644 --- a/packages/noir/example/widgets_tour.dart +++ b/packages/noir/example/widgets_tour.dart @@ -1,5 +1,3 @@ -// Run with: dart run example/widgets_tour.dart -// // Combined tour of Select, ScrollBox, and TextArea. // Press Tab / Shift+Tab to move focus. Ctrl+D submits the TextArea. // Esc always quits. q quits unless the TextArea is focused. diff --git a/packages/noir/test/architecture/package_distribution_ownership_test.dart b/packages/noir/test/architecture/package_distribution_ownership_test.dart index ff6a2b03..cab62840 100644 --- a/packages/noir/test/architecture/package_distribution_ownership_test.dart +++ b/packages/noir/test/architecture/package_distribution_ownership_test.dart @@ -886,7 +886,6 @@ void main() { for (final path in const [ 'tool/patch_manager.dart', 'tool/patch_manager/patch_manager.dart', - 'tool/snapshot_scenes.dart', 'example/src/chat/app.dart', 'example/src/chat/protocol.dart', 'example/src/chat/session_controller.dart', diff --git a/packages/noir/test/example/framework_primitives_test.dart b/packages/noir/test/example/framework_primitives_test.dart index badd9497..515e0e48 100644 --- a/packages/noir/test/example/framework_primitives_test.dart +++ b/packages/noir/test/example/framework_primitives_test.dart @@ -23,7 +23,7 @@ void main() { final count = frame.findText('Count: 0').single; final digitX = count.x + 'Count: '.length; - expect(frame.getForegroundColor(digitX, count.y), Color.yellow); + expect(frame.getForegroundColor(digitX, count.y), ThemeData.dark.text); expect(frame.getCell(digitX, count.y).isBold, isTrue); } finally { app.dispose(); diff --git a/packages/noir/test/goldens/chat_demo_64x18.buffer.txt b/packages/noir/test/goldens/chat_demo_64x18.buffer.txt index 28381cfc..940e3b19 100644 --- a/packages/noir/test/goldens/chat_demo_64x18.buffer.txt +++ b/packages/noir/test/goldens/chat_demo_64x18.buffer.txt @@ -5,12 +5,12 @@ › Inspect the current change. ┌─ › Permission required ────────────────────────────────┐ - │Edit │ + ● │Edit │ │ │ - │Update the focused regression test │ + ● │Update the focused regression test │s │ │ - │ Deny Allow │ - └────────────────────────────────────────────────────────┘ + ──│ Deny Allow │── + … └────────────────────────────────────────────────────────┘ ────────────────────────────────────────────────────────────── Ctrl+J newline · Enter send · Shift+Tab mode... 0 tok · $0.000 diff --git a/packages/noir/test/goldens/chat_demo_64x18.styles.txt b/packages/noir/test/goldens/chat_demo_64x18.styles.txt index 7837b3c6..97e55942 100644 --- a/packages/noir/test/goldens/chat_demo_64x18.styles.txt +++ b/packages/noir/test/goldens/chat_demo_64x18.styles.txt @@ -20,7 +20,9 @@ 0,6:#ffffffff|#090a0bff|0 * 3 3,6:#66d9ffff|#0f1013ff|0 * 58 61,6:#ffffffff|#090a0bff|0 * 3 -0,7:#ffffffff|#090a0bff|0 * 3 +0,7:#ffffffff|#090a0bff|0 +1,7:#999999ff|#090a0bff|0 +2,7:#ffffffff|#090a0bff|0 3,7:#66d9ffff|#0f1013ff|0 4,7:#ffffffff|#0f1013ff|1 * 4 8,7:#ffffffff|#0f1013ff|0 * 52 @@ -31,17 +33,21 @@ 4,8:#ffffffff|#0f1013ff|0 * 56 60,8:#66d9ffff|#0f1013ff|0 61,8:#ffffffff|#090a0bff|0 * 3 -0,9:#ffffffff|#090a0bff|0 * 3 +0,9:#ffffffff|#090a0bff|0 +1,9:#999999ff|#090a0bff|0 +2,9:#ffffffff|#090a0bff|0 3,9:#66d9ffff|#0f1013ff|0 4,9:#ffffffff|#0f1013ff|0 * 56 60,9:#66d9ffff|#0f1013ff|0 -61,9:#ffffffff|#090a0bff|0 * 3 +61,9:#999999ff|#090a0bff|0 +62,9:#ffffffff|#090a0bff|0 * 2 0,10:#ffffffff|#090a0bff|0 * 3 3,10:#66d9ffff|#0f1013ff|0 4,10:#ffffffff|#0f1013ff|0 * 56 60,10:#66d9ffff|#0f1013ff|0 61,10:#ffffffff|#090a0bff|0 * 3 -0,11:#ffffffff|#090a0bff|0 * 3 +0,11:#ffffffff|#090a0bff|0 +1,11:#2e3847ff|#090a0bff|0 * 2 3,11:#66d9ffff|#0f1013ff|0 4,11:#ffffffff|#0f1013ff|0 * 43 47,11:#ffffffff|#0f1013ff|1 * 4 @@ -50,8 +56,11 @@ 54,11:#051a29ff|#66d9ffff|0 * 5 59,11:#ffffffff|#66d9ffff|0 60,11:#66d9ffff|#0f1013ff|0 -61,11:#ffffffff|#090a0bff|0 * 3 -0,12:#ffffffff|#090a0bff|0 * 3 +61,11:#2e3847ff|#090a0bff|0 * 2 +63,11:#ffffffff|#090a0bff|0 +0,12:#ffffffff|#090a0bff|0 +1,12:#ff9900ff|#090a0bff|1 +2,12:#ffffffff|#090a0bff|0 3,12:#66d9ffff|#0f1013ff|0 * 58 61,12:#ffffffff|#090a0bff|0 * 3 0,13:#ffffffff|#090a0bff|0 diff --git a/packages/noir/test/tools/mcp_inspector_drive_test.dart b/packages/noir/test/tools/mcp_inspector_drive_test.dart index 8fc8ba87..a7f3b79f 100644 --- a/packages/noir/test/tools/mcp_inspector_drive_test.dart +++ b/packages/noir/test/tools/mcp_inspector_drive_test.dart @@ -560,7 +560,7 @@ Future _visibleRowCount(NoirDriver driver) async { /// The command the inspector runs for [fixture], compiled on first use. /// -/// [fixture] is relative to `tools/mcp_fixtures`, which depends only on +/// [fixture] is relative to `tool/mcp_fixtures`, which depends only on /// `package:mcp_dart` and so has no native-assets build hook. That matters /// twice. `dart run` on a hook-bearing package writes hook progress to /// **stdout** — the MCP protocol channel — and `--verbosity=error` suppresses diff --git a/packages/noir/tool/analysis_options.yaml b/packages/noir/tool/analysis_options.yaml new file mode 100644 index 00000000..b1e02ebe --- /dev/null +++ b/packages/noir/tool/analysis_options.yaml @@ -0,0 +1,6 @@ +# Development tools are not the published API. Keep every other package lint. +include: ../analysis_options.yaml + +linter: + rules: + public_member_api_docs: false diff --git a/packages/noir/tool/mcp_fixtures/README.md b/packages/noir/tool/mcp_fixtures/README.md index a336f206..ade9b658 100644 --- a/packages/noir/tool/mcp_fixtures/README.md +++ b/packages/noir/tool/mcp_fixtures/README.md @@ -18,3 +18,5 @@ boundary. | `legacy_elicit_server` | `--protocol legacy` | `register_user` calls `elicitation/create` from inside its tool callback | | `selection_server` | `--protocol legacy` | Two tools with distinct fields, for focus across form replacement | | `long_form_server` | `--protocol legacy` | One tool with eight fields, for a scrolling modal | +| `many_tools_server` | default | Twenty echo tools, `tool01` through `tool20`, enough to overflow the primitives list | +| `slow_server` | default | `slow` echoes a note after a delay, so a check can observe a request still in flight | diff --git a/packages/noir/tool/mcp_inspector/FINDINGS.md b/packages/noir/tool/mcp_inspector/FINDINGS.md index 1c38de3f..fd0d755d 100644 --- a/packages/noir/tool/mcp_inspector/FINDINGS.md +++ b/packages/noir/tool/mcp_inspector/FINDINGS.md @@ -19,114 +19,6 @@ The inspector imports only `package:noir/noir.dart` and ## Noir framework gaps -### 1. An application could not measure the terminal — fixed - -`ListView` occupies exactly its `height` rows -(`lib/src/widgets/list_view.dart`), and the application surface exposed no -viewport size: there was no `LayoutBuilder`, no `MediaQuery`, and no size on -the `TuiApp` handle. An application that wanted a list to fill its pane had to -hard-code a row budget for the smallest grid it supports. - -*Original reproduction.* `lib/src/ui/primitives_pane.dart` set -`primitiveListRows` to 12, chosen for a 60x18 grid. At 100x30 the list left -unused rows in the pane. - -*Fix.* `package:noir/noir.dart` exports `LayoutBuilder` and -`LayoutWidgetBuilder`. The builder runs during layout and its child is laid out -in the same frame, so the constraints it reads are the constraints its child -receives. `ListView` also follows its highlight when its own height changes, so -a resized list keeps the selected row on screen. - -*Effect on this tool.* The primitives pane derives its list height and its -scroll indicator from the rows the pane actually offers. -`primitiveListFallbackRows` remains only for an unbounded host. The inspector's -forms still use bounded scroll viewports, so their field count does not push -Run or the modal actions outside the pane. - -*Layer.* Widgets and layout. - -*Classification.* Noir framework gap, now closed. - -### 2. A keyed `ListView` row resolved to two driver nodes — fixed - -`ListView._buildRow` used to copy the row's own `LocalKey` onto the `SizedBox` -it wraps the row in, so the key existed twice in the element tree. A strict -driver locator then failed. - -*Original reproduction.* Give a row `key: ValueKey('primitive:calculate')` and -run `wait key primitive:calculate` through `noir_driver:drive`: - -``` -Bad state: Strict locator key "primitive:calculate" resolved to 2 matches. - - SizedBox(key: primitive:calculate, ...) - - Text(key: primitive:calculate, ...) -``` - -*Fix.* `ListView` now derives a private `_ListRowKey` from the row's key for -the wrapper, so the wrapper still reconciles by row identity and the row's own -key names exactly one element. `test/widgets/list_view_test.dart`, group -*ListView row identity*, covers the single match, state preservation across -scrolling and reordering, and disposal of a row that leaves the window. - -*Effect on this tool.* The row builder puts the key on the `Align` it already -needed for the full-width highlight. No extra wrapper remains. - -*Layer.* Widgets and the drive-mode tree. - -*Classification.* Noir framework gap, now closed. - -### 3. Losing the focused node silently disabled every `Shortcuts` binding — fixed - -`Shortcuts.handleKeyEvent` walks ancestors starting at the focused element -(`lib/src/widgets/shortcuts.dart:110-124`). When nothing owns primary focus -there is no starting point, so no binding fired and no error was reported. -Noir repaired escaped focus inside a `Modal`, but the root scope did not -repair it. - -Two ordinary application actions left the tree unfocused: - -- Disabling the focused control. `Button(onPressed: null)` while a request is - in flight dropped focus. `find focused` then reported *No node in this tree - has primary focus*, and Ctrl+N stopped switching tabs until the user clicked - or pressed Tab. -- Removing the region that owns focus. Switching from a tab whose detail pane - holds focus to one that does not build that pane left the focused node - detached. - -*Fix.* `FocusManager` now schedules focus recovery when disabling or removing -the focused control leaves no primary focus. It records the enclosing scopes at -the loss, while the chain is still whole, then runs after the synchronous tree -updates finish. Recovery takes the nearest recorded scope that survived and can -hold focus, otherwise the first node in traversal order, and leaves focus empty -when nothing is eligible. A deliberate focus decision supersedes a queued -recovery: `FocusNode.requestFocus()` and `FocusNode.unfocus()` both drop it, so -an incoming `autofocus` wins and an intentional clear stays cleared even when -an earlier involuntary loss is still queued. `test/widgets/focus_recovery_test.dart` covers each case. - -*Effect on this tool.* The Run button is disabled while a request is in -flight, which is what a user expects to see; `InspectorController.run` keeps -its second-call guard. `_syncTabFocus` still hands focus to the primitives -list on a tab change, but only when that list is attached: an empty tab builds -no list at all. `test/tools/mcp_inspector_drive_test.dart`, *shortcuts keep -working while a request is in flight*, drives the disabled-Run case against a -fixture that answers slowly. - -*Layer.* Focus manager and `Shortcuts`. - -*Classification.* Noir framework gap, now closed. - -The schema view (originally Ctrl+O, now Ctrl+G) reproduced this twice more. -Swapping the form out for the schema removes whatever field owned focus, and -every binding stopped, including Ctrl+O itself, Ctrl+N, and Ctrl+Q, leaving -only Ctrl+C. Enter on a list row -then threw `FocusNode is not attached to a FocusManager`, because the code -still focused the first field awaiting input after that field left the tree. - -The inspector keeps the pattern `_syncTabFocus` uses: the incoming region -takes focus through `autofocus`, and the outgoing swap hands focus to a widget -that is mounted in both states. Recovery is the backstop for what an -application misses, not a replacement for a deliberate focus target. - ### 4. Disposing an attached `FocusNode` throws on the next dispatch An application that generates one field per selection wants to dispose the @@ -259,19 +151,6 @@ the text and reports `not a number` or `not an integer` next to the field name. That is enough for a form, but every numeric tool argument needs the same application-side coercion. -## Real-terminal observations - -### 15. The screen holds up in iTerm2 at three grids - -The original authorized iTerm2 run at 100x40, 80x24, and 60x18 confirmed the -layout, the generated form, the paired protocol log, the console, and the elicitation -modal. Before the form-scroll fix, the result region lost every row at -60x18. Every session closed cleanly on Ctrl+C with no cursor or mode -damage, so the documented iTerm cursor restoration limitation did not -appear. Evidence and the side-by-side comparison with the official MCP -Inspector TUI live in an untracked run folder under -`.context/terminal-evidence/`. - ## mcp_dart and Dart tooling observations ### 16. `dart run` writes build-hook progress to stdout @@ -300,13 +179,13 @@ hooks: *'dart compile' does not support build hooks, use 'dart build' instead*. `dart build cli` only accepts a target inside `bin/`, so neither command reaches a fixture that inherits the hook. -*Fix in this package.* The fixtures moved to `tools/mcp_fixtures`, which +*Fix in this package.* The fixtures moved to `tool/mcp_fixtures`, which depends on `package:mcp_dart` alone and therefore has no build hook. `lib/` builds each server without a transport for the in-process tests, and `bin/` starts it on stdio. Tests compile the entry point with `dart compile exe`, which that package permits, and start the executable, so its stdout carries only what the server writes on every supported SDK. -`tools/mcp_inspector/test/compiled_fixture.dart` and the matching helper in +`tool/mcp_inspector/test/compiled_fixture.dart` and the matching helper in `test/tools/mcp_inspector_drive_test.dart` own that step, and `test/tools/mcp_inspector_package_test.dart` asserts that the fixture package keeps its single dependency. @@ -436,18 +315,6 @@ but the pinned MCP SDK rejects it before delivery to the inspector. Custom retain the value as text and the schema view explains that JSON display is unavailable. This guard is covered with a fake session, not a live wire claim. -### 25. Lifecycle tests used to count unrelated inspector processes - -`test/stdio_lifecycle_test.dart` asserts that `close()` leaves no child, and -counts with `pgrep -f fixtures/calculate_server.dart`. The pattern matches -every process on the machine, so an interactive -`dart run bin/mcp_inspector.dart -- dart run ... fixtures/calculate_server.dart` -session running beside the suite failed the test. Both lifetime checks now -count a unique process argument generated per run. The package test also keeps -a second live fixture connected, closes the tested session, and successfully -calls the second fixture afterward. This proves isolation without requiring -the user to close an interactive session. - ### 26. Default macOS terminal modes consume Ctrl+O and Ctrl+Q The authorized follow-up in iTerm2 reproduced two shortcuts that headless drive @@ -466,9 +333,7 @@ were scrollable, all eight modal values were accepted, and the greeting flow handled Accept, Cancel, and Decline. The lifecycle test passed while a separate interactive inspector stayed open. The footer also fits every shortcut at the 60x18 floor. Every owned window closed through Ctrl+X and existing sessions -were preserved. Screenshots and route logs remain under -`.context/terminal-evidence/20260904T223955Z-0bdf554/`. - +were preserved. ### 27. Row activation can precede the new field's attachment diff --git a/packages/noir/tool/mcp_inspector/README.md b/packages/noir/tool/mcp_inspector/README.md index 543c09f6..ad4ff9e2 100644 --- a/packages/noir/tool/mcp_inspector/README.md +++ b/packages/noir/tool/mcp_inspector/README.md @@ -9,17 +9,17 @@ resources, and prompts, generates a form from each input schema, records every JSON-RPC message, shows the server's standard error output, and answers server-initiated input requests from a modal. -This package is not published. It lives in `tools/`, which `.pubignore` +This package is not published. It lives in `tool/`, which `.pubignore` excludes from the Noir archive. ## Run it ```sh -cd tools/mcp_inspector +cd tool/mcp_inspector dart pub get # Compile the fixture, then start it as a child process. -# From tools/mcp_inspector: +# From tool/mcp_inspector: (cd ../mcp_fixtures && dart pub get && dart compile exe bin/calculate_server.dart -o .dart_tool/calculate_server) dart run bin/mcp_inspector.dart -- ../mcp_fixtures/.dart_tool/calculate_server @@ -43,7 +43,7 @@ and is also the path used by the automated integration tests. The child's stdout is the protocol channel, so nothing else may write to it. In a package whose dependency graph has a native-assets build hook, `dart run` writes `Running build hooks...` there. `--verbosity=error` suppresses that only -from Dart 3.11 on. The fixtures live in `tools/mcp_fixtures`, which depends on +from Dart 3.11 on. The fixtures live in `tool/mcp_fixtures`, which depends on `package:mcp_dart` alone and therefore has no hook. Give a server of your own either a hook-free package or a compiled executable. See [`FINDINGS.md`](FINDINGS.md), entry 16. @@ -160,10 +160,3 @@ printf 'wait key primitive:calculate\nkey enter\ntype 5\nkey tab\ntype 3\nclick [`FINDINGS.md`](FINDINGS.md) records every Noir gap, documented behavior, and mcp_dart observation this tool produced. - -One authorized iTerm2 run captured this inspector and the official MCP -Inspector TUI on the same server surface at a 100x40 grid, plus the 80x24 and -60x18 floors and the elicitation modal. The screenshots, `COMPARISON.md`, and -`NOTES.md` live in an untracked run folder under -`.context/terminal-evidence/`, because `.context/` is workspace state rather -than repository source. diff --git a/packages/noir/tool/mcp_inspector/bin/mcp_inspector.dart b/packages/noir/tool/mcp_inspector/bin/mcp_inspector.dart index 5c190323..2fec2149 100644 --- a/packages/noir/tool/mcp_inspector/bin/mcp_inspector.dart +++ b/packages/noir/tool/mcp_inspector/bin/mcp_inspector.dart @@ -1,6 +1,6 @@ /// Command-line entry point for the Noir MCP inspector. /// -/// From `tools/mcp_inspector`, start a server as a child process: +/// From `tool/mcp_inspector`, start a server as a child process: /// /// ```sh /// (cd ../mcp_fixtures && dart pub get && dart compile exe bin/calculate_server.dart -o .dart_tool/calculate_server) diff --git a/packages/noir/tool/mcp_inspector/test/compiled_fixture.dart b/packages/noir/tool/mcp_inspector/test/compiled_fixture.dart index 7baba13f..dde3d883 100644 --- a/packages/noir/tool/mcp_inspector/test/compiled_fixture.dart +++ b/packages/noir/tool/mcp_inspector/test/compiled_fixture.dart @@ -10,7 +10,7 @@ import 'dart:io'; /// protocol channel, so that text corrupts the server's first JSON-RPC line /// and the handshake times out. /// -/// The fixtures therefore live in `tools/mcp_fixtures`, which depends only on +/// The fixtures therefore live in `tool/mcp_fixtures`, which depends only on /// `package:mcp_dart` and so has no build hook at all. That also lets /// `dart compile exe` run: the same SDK rejects it for a package graph that /// does have hooks. A compiled executable then carries only what the server diff --git a/packages/noir/tool/screenshot_iterm.sh b/packages/noir/tool/screenshot_iterm.sh deleted file mode 100755 index 1f1f6927..00000000 --- a/packages/noir/tool/screenshot_iterm.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# Capture a bitmap screenshot of an iTerm2 window by its title. -# Usage: ./tool/screenshot_iterm.sh [out.png] -set -euo pipefail - -title="${1:-}" -out="${2:-.context/screenshots/$(date +%s).png}" - -if [[ -z "$title" ]]; then - echo "usage: $0 [out.png]" >&2 - exit 2 -fi - -mkdir -p "$(dirname "$out")" -wid="$(GetWindowID iTerm2 "$title")" -screencapture -l "$wid" -x "$out" -echo "$out" diff --git a/packages/noir/tool/snapshot_scenes.dart b/packages/noir/tool/snapshot_scenes.dart deleted file mode 100644 index ceee0eb0..00000000 --- a/packages/noir/tool/snapshot_scenes.dart +++ /dev/null @@ -1,199 +0,0 @@ -// ignore_for_file: prefer_constructors_over_static_methods -// Minimal snapshot scenes for Dart side parity checks. -// Usage: -// dart run tool/snapshot_scenes.dart --scene S1 --width 20 --height 5 -// Scenes: -// S1: text_hello -// S2: box_20x5 (bordered, filled) -// S3: padded_text (12x5 region with 1-cell padding) -// W1: Text('Hello') -// W2: default Row with Left, Middle, Right -// W3: default Column with Line 1, Line 2, Line 3 - -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:noir/noir.dart' as ui; -import 'package:noir/noir_low_level.dart'; - -import '../test/helpers/buffer_capture.dart'; - -void main(List args) async { - final argz = _Args.parse(args); - final Map jsonOut; - - switch (argz.scene) { - case 'S1': - case 'S2': - case 'S3': - jsonOut = _capturePrimitiveScene(argz); - case 'W1': - jsonOut = captureWidgetSceneSnapshot( - const ui.Text('Hello'), - width: argz.width, - height: argz.height, - ); - case 'W2': - jsonOut = captureWidgetSceneSnapshot( - const ui.Row( - children: [ui.Text('Left'), ui.Text('Middle'), ui.Text('Right')], - ), - width: argz.width, - height: argz.height, - ); - case 'W3': - jsonOut = captureWidgetSceneSnapshot( - const ui.Column( - children: [ui.Text('Line 1'), ui.Text('Line 2'), ui.Text('Line 3')], - ), - width: argz.width, - height: argz.height, - ); - default: - io.stderr.writeln('Unknown scene: ${argz.scene}'); - io.exitCode = 2; - return; - } - - io.stdout.write(const JsonEncoder.withIndent(' ').convert(jsonOut)); -} - -/// Captures one widget scene through the repository's real test render path. -Map captureWidgetSceneSnapshot( - ui.Widget widget, { - required int width, - required int height, -}) { - final capture = BufferCapture( - width: width, - height: height, - layoutConstraints: ui.BoxConstraints.tight(width: width, height: height), - ); - try { - return _snapshotFromCapturedBuffer(capture.capture(widget)); - } finally { - capture.dispose(); - } -} - -Map _capturePrimitiveScene(_Args argz) { - final renderer = Renderer.create(argz.width, argz.height); - try { - final buffer = renderer.nextBuffer..clear(ui.Color.transparent); - - switch (argz.scene) { - case 'S1': - _sceneTextHello(buffer); - case 'S2': - _sceneBox(buffer); - case 'S3': - _scenePaddedText(buffer); - default: - throw StateError('Not a primitive scene: ${argz.scene}'); - } - - return _snapshotFromBuffer(buffer); - } finally { - renderer.dispose(); - } -} - -Map _snapshotFromCapturedBuffer(CapturedBuffer captured) => { - 'version': '1', - 'width': captured.width, - 'height': captured.height, - 'cells': [ - for (var y = 0; y < captured.height; y++) - for (var x = 0; x < captured.width; x++) - _capturedCellSnapshot(captured, x, y), - ], -}; - -Map _capturedCellSnapshot( - CapturedBuffer captured, - int x, - int y, -) { - final foreground = captured.getForegroundColor(x, y); - final background = captured.getBackgroundColor(x, y); - return { - 'ch': captured.getChar(x, y), - 'fg': [foreground.r, foreground.g, foreground.b, foreground.a], - 'bg': [background.r, background.g, background.b, background.a], - }; -} - -Map _snapshotFromBuffer(Buffer buffer) { - final direct = buffer.getDirectAccess(); - return { - 'version': '1', - 'width': direct.width, - 'height': direct.height, - 'cells': [ - for (var y = 0; y < direct.height; y++) - for (var x = 0; x < direct.width; x++) - _directCellSnapshot(direct, x, y), - ], - }; -} - -Map _directCellSnapshot( - DirectBufferAccess direct, - int x, - int y, -) { - final foreground = direct.getForeground(x, y); - final background = direct.getBackground(x, y); - return { - 'ch': direct.getChar(x, y), - 'fg': [foreground.r, foreground.g, foreground.b, foreground.a], - 'bg': [background.r, background.g, background.b, background.a], - }; -} - -void _sceneTextHello(Buffer buffer) { - // Draw "Hello" at 0,0 in white. - buffer.drawText('Hello', 0, 0, ui.Color.white); -} - -void _sceneBox(Buffer buffer) { - // 20x5 bordered box, cyan border on dark background fill. - // Positioned at 0,0 and assumes width>=20, height>=5. - buffer.drawBox( - 0, - 0, - 20, - 5, - ui.BoxOptions(fill: true), - ui.Color.cyan, // cyan border - const ui.Color(0.1, 0.1, 0.15), // dark fill - ); -} - -void _scenePaddedText(Buffer buffer) { - // 12x5 area with a background, 1-cell padding, text inside. - // Top-left at 0,0 for determinism. - buffer.fillRect(0, 0, 12, 5, const ui.Color(0.05, 0.05, 0.08)); - buffer.drawText('Inner', 1, 1, ui.Color.white); -} - -class _Args { - _Args(this.scene, this.width, this.height); - final String scene; - final int width; - final int height; - - static _Args parse(List args) { - var scene = 'S1'; - var width = 20; - var height = 5; - - for (var i = 0; i < args.length; i++) { - final a = args[i]; - if (a == '--scene' && i + 1 < args.length) scene = args[++i]; - if (a == '--width' && i + 1 < args.length) width = int.parse(args[++i]); - if (a == '--height' && i + 1 < args.length) height = int.parse(args[++i]); - } - return _Args(scene, width, height); - } -}