From 276865c6c238399fdbe58220ddc3cca5354af81d Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Mon, 27 Jul 2026 11:19:30 +0300 Subject: [PATCH 1/4] fix(sandbox): require consent before unsandboxed driver launch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #395 — fail-closed when OS sandbox wrapper unavailable; user must confirm via dialog on Linux (no bwrap) and Windows until AppContainer. --- docs/security.md | 13 ++ .../extensions/extension_driver_session.dart | 46 +++++- .../extensions/rpc/plugin_rpc_bridge.dart | 2 + .../sandbox/sandbox_os_isolation.dart | 58 +++++++ .../sandbox/sandbox_process_runner.dart | 22 ++- .../unsandboxed_launch_consent_gate.dart | 21 +++ .../unsandboxed_driver_consent_dialog.dart | 52 ++++++ lib/features/main_screen/main_screen.dart | 12 ++ .../sandbox/sandbox_os_isolation_test.dart | 48 ++++++ .../sandbox/sandbox_process_runner_test.dart | 54 +++++++ ...0\265\320\271\321\201\320\276\320\262.txt" | 148 ++++++++++++++++++ 11 files changed, 470 insertions(+), 6 deletions(-) create mode 100644 lib/core/extensions/sandbox/sandbox_os_isolation.dart create mode 100644 lib/core/extensions/sandbox/unsandboxed_launch_consent_gate.dart create mode 100644 lib/features/extensions/presentation/widgets/unsandboxed_driver_consent_dialog.dart create mode 100644 test/core/extensions/sandbox/sandbox_os_isolation_test.dart create mode 100644 "\320\220\320\275\320\260\320\273\320\270\320\267 Fluid Morphing \320\230\320\275\321\202\320\265\321\200\321\204\320\265\320\271\321\201\320\276\320\262.txt" diff --git a/docs/security.md b/docs/security.md index 580174e1..32f46518 100644 --- a/docs/security.md +++ b/docs/security.md @@ -37,3 +37,16 @@ Marketplace downloads, local extension sideload (`.zip` / `.qext`), and in-app u Archives exceeding these bounds fail closed before files are written to disk. Path traversal checks remain in `archive_path_guard.dart`. +## Extension driver OS sandbox + +Process-sandbox database drivers launch inside OS-level isolation when available: + +| Platform | Wrapper | When unavailable | +|----------|---------|------------------| +| Linux | `bwrap` (bubblewrap) | User must confirm **Run without OS sandbox** | +| macOS | `sandbox-exec` (Seatbelt) | N/A — always wrapped | +| Windows | AppContainer (planned) | User must confirm until native helper ships | + +Querya refuses **silent** unsandboxed launch. `SandboxProcessRunner` throws `SandboxOsIsolationUnavailableException` until the user approves via the consent dialog registered from the main window. + +**Linux:** install `bubblewrap` and ensure unprivileged user namespaces are enabled if you want OS sandbox without manual confirmation. \ No newline at end of file diff --git a/lib/core/extensions/extension_driver_session.dart b/lib/core/extensions/extension_driver_session.dart index 31f46f13..2c22e87f 100644 --- a/lib/core/extensions/extension_driver_session.dart +++ b/lib/core/extensions/extension_driver_session.dart @@ -11,6 +11,8 @@ import 'package:querya_desktop/core/extensions/models/extension_manifest.dart'; import 'package:querya_desktop/core/extensions/models/extension_object_metadata.dart'; import 'package:querya_desktop/core/extensions/models/extension_server_stats.dart'; import 'package:querya_desktop/core/extensions/rpc/plugin_rpc_bridge.dart'; +import 'package:querya_desktop/core/extensions/sandbox/sandbox_os_isolation.dart'; +import 'package:querya_desktop/core/extensions/sandbox/unsandboxed_launch_consent_gate.dart'; import 'package:querya_desktop/core/sdui/sdui_tree_schema.dart'; import 'package:querya_desktop/core/storage/connection_secrets_store.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; @@ -124,18 +126,52 @@ class ExtensionDriverSession { } final bridge = bridgeFactory?.call() ?? PluginRpcBridge(); - await bridge.start( + await _startBridgeWithConsent( + bridge: bridge, manifest: manifest, pluginExecutable: executable, extensionRoot: root, - handshakeParams: { - 'queryaVersion': '2.0.0', - 'pluginId': manifest.id, - }, ); return bridge; } + Future _startBridgeWithConsent({ + required PluginRpcBridge bridge, + required ExtensionManifest manifest, + required String pluginExecutable, + required String extensionRoot, + }) async { + const handshakeParams = { + 'queryaVersion': '2.0.0', + }; + + try { + await bridge.start( + manifest: manifest, + pluginExecutable: pluginExecutable, + extensionRoot: extensionRoot, + handshakeParams: { + ...handshakeParams, + 'pluginId': manifest.id, + }, + ); + } on SandboxOsIsolationUnavailableException catch (error) { + final approved = + await UnsandboxedLaunchConsentGate.instance.request(error); + if (!approved) throw error; + await bridge.start( + manifest: manifest, + pluginExecutable: pluginExecutable, + extensionRoot: extensionRoot, + allowUnsandboxedLaunch: true, + handshakeParams: { + ...handshakeParams, + 'pluginId': manifest.id, + }, + ); + } + } + Future _injectAndConnect( PluginRpcBridge bridge, { required int connectionId, diff --git a/lib/core/extensions/rpc/plugin_rpc_bridge.dart b/lib/core/extensions/rpc/plugin_rpc_bridge.dart index 89153785..9e528bb3 100644 --- a/lib/core/extensions/rpc/plugin_rpc_bridge.dart +++ b/lib/core/extensions/rpc/plugin_rpc_bridge.dart @@ -62,6 +62,7 @@ class PluginRpcBridge { String? extensionRoot, Map? environment, Map? handshakeParams, + bool allowUnsandboxedLaunch = false, }) async { if (_started) { throw StateError('PluginRpcBridge already started'); @@ -77,6 +78,7 @@ class PluginRpcBridge { extensionRoot: extensionRoot ?? manifest.installPath, capabilities: capabilities, environment: environment, + allowUnsandboxedLaunch: allowUnsandboxedLaunch, ); _handle = handle; diff --git a/lib/core/extensions/sandbox/sandbox_os_isolation.dart b/lib/core/extensions/sandbox/sandbox_os_isolation.dart new file mode 100644 index 00000000..53d381ee --- /dev/null +++ b/lib/core/extensions/sandbox/sandbox_os_isolation.dart @@ -0,0 +1,58 @@ +import 'sandbox_launch_command.dart'; + +/// Thrown when a process-sandbox driver would launch without OS-level isolation. +class SandboxOsIsolationUnavailableException implements Exception { + const SandboxOsIsolationUnavailableException({ + required this.platform, + required this.message, + this.installHint, + }); + + final String platform; + final String message; + final String? installHint; + + @override + String toString() => 'SandboxOsIsolationUnavailableException: $message'; +} + +/// Describes why OS sandboxing is unavailable for a launch command. +abstract final class SandboxOsIsolation { + static SandboxOsIsolationUnavailableException? exceptionForLaunchCommand( + SandboxLaunchCommand command, + ) { + if (command.usesOsSandbox) return null; + + switch (command.platform) { + case 'linux': + return const SandboxOsIsolationUnavailableException( + platform: 'linux', + message: + 'OS sandbox (bubblewrap) is not available on this Linux system.', + installHint: + 'Install bubblewrap (bwrap) from your distribution and ensure ' + 'unprivileged user namespaces are enabled, or confirm below to ' + 'run the driver without OS sandbox.', + ); + case 'windows': + return const SandboxOsIsolationUnavailableException( + platform: 'windows', + message: + 'Native OS sandbox is not yet available for extension drivers ' + 'on Windows.', + installHint: + 'Drivers run with soft isolation only until AppContainer support ' + 'lands. Confirm below only if you trust this extension.', + ); + default: + return SandboxOsIsolationUnavailableException( + platform: command.platform, + message: + 'OS sandbox is not available for extension drivers on ' + '${command.platform}.', + installHint: + 'Confirm below only if you trust this extension package.', + ); + } + } +} diff --git a/lib/core/extensions/sandbox/sandbox_process_runner.dart b/lib/core/extensions/sandbox/sandbox_process_runner.dart index 09d86146..e793eac1 100644 --- a/lib/core/extensions/sandbox/sandbox_process_runner.dart +++ b/lib/core/extensions/sandbox/sandbox_process_runner.dart @@ -4,6 +4,7 @@ import 'dart:io'; import 'package:flutter/foundation.dart'; import 'package:querya_desktop/core/extensions/models/sandbox_capabilities.dart'; import 'package:querya_desktop/core/extensions/sandbox/sandbox_launch_command.dart'; +import 'package:querya_desktop/core/extensions/sandbox/sandbox_os_isolation.dart'; import 'package:querya_desktop/core/extensions/sandbox/sandbox_scratch_directory.dart'; import 'package:querya_desktop/core/extensions/sandbox/sandbox_secret_guard.dart'; @@ -100,6 +101,9 @@ class SandboxProcessRunner { /// Spawns [pluginExecutable] inside the OS sandbox for [pluginId]. /// + /// When OS sandboxing is unavailable, launch fails unless + /// [allowUnsandboxedLaunch] is true (requires explicit user consent in UI). + /// /// Credentials must never be passed via [pluginArguments] or [environment]; /// use [SandboxCredentialsInjector] over Stdio JSON-RPC instead. Future start({ @@ -109,6 +113,7 @@ class SandboxProcessRunner { String? extensionRoot, SandboxCapabilities? capabilities, Map? environment, + bool allowUnsandboxedLaunch = false, }) async { SandboxSecretGuard.assertNoSecrets( arguments: pluginArguments, @@ -125,7 +130,8 @@ class SandboxProcessRunner { if (bwrapAvailable == null && !usesBwrap) { debugPrint( 'SandboxProcessRunner: bubblewrap unavailable or cannot set up user ' - 'namespaces on this system; launching $pluginId without OS sandbox.', + 'namespaces on this system; $pluginId requires consent to launch ' + 'without OS sandbox.', ); } final command = SandboxLaunchCommand.build( @@ -138,6 +144,20 @@ class SandboxProcessRunner { bwrapAvailable: usesBwrap, ); + final isolationIssue = + SandboxOsIsolation.exceptionForLaunchCommand(command); + if (isolationIssue != null && !allowUnsandboxedLaunch) { + await scratch.delete(); + throw isolationIssue; + } + + if (isolationIssue != null) { + debugPrint( + 'SandboxProcessRunner: launching $pluginId without OS sandbox after ' + 'explicit consent (${command.platform}).', + ); + } + // Never forward parent secrets via environment. Only pass an explicit map // (credentials go through Stdio JSON-RPC — Block E §5). final sanitizedEnv = { diff --git a/lib/core/extensions/sandbox/unsandboxed_launch_consent_gate.dart b/lib/core/extensions/sandbox/unsandboxed_launch_consent_gate.dart new file mode 100644 index 00000000..4bd2df05 --- /dev/null +++ b/lib/core/extensions/sandbox/unsandboxed_launch_consent_gate.dart @@ -0,0 +1,21 @@ +import 'sandbox_os_isolation.dart'; + +typedef UnsandboxedLaunchConsentHandler = Future Function( + SandboxOsIsolationUnavailableException details, +); + +/// App-level hook for explicit user consent before unsandboxed driver launch. +class UnsandboxedLaunchConsentGate { + UnsandboxedLaunchConsentGate._(); + + static final UnsandboxedLaunchConsentGate instance = + UnsandboxedLaunchConsentGate._(); + + UnsandboxedLaunchConsentHandler? handler; + + Future request(SandboxOsIsolationUnavailableException details) async { + final callback = handler; + if (callback == null) return false; + return callback(details); + } +} diff --git a/lib/features/extensions/presentation/widgets/unsandboxed_driver_consent_dialog.dart b/lib/features/extensions/presentation/widgets/unsandboxed_driver_consent_dialog.dart new file mode 100644 index 00000000..dbba764d --- /dev/null +++ b/lib/features/extensions/presentation/widgets/unsandboxed_driver_consent_dialog.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart' as material; +import 'package:querya_desktop/core/extensions/sandbox/sandbox_os_isolation.dart'; +import 'package:querya_desktop/shared/widgets/widgets.dart'; + +Future showUnsandboxedDriverConsentDialog( + material.BuildContext context, + SandboxOsIsolationUnavailableException details, +) async { + final approved = await showAppDialog( + context: context, + builder: (dialogContext) => material.AlertDialog( + title: const material.Text('Run driver without OS sandbox?'), + content: material.SizedBox( + width: 440, + child: material.Column( + mainAxisSize: material.MainAxisSize.min, + crossAxisAlignment: material.CrossAxisAlignment.start, + children: [ + material.Text(details.message), + if (details.installHint != null) ...[ + const material.SizedBox(height: 12), + material.Text( + details.installHint!, + style: material.TextStyle( + fontSize: 13, + color: Theme.of(dialogContext).colorScheme.mutedForeground, + ), + ), + ], + const material.SizedBox(height: 12), + const material.Text( + 'The driver process may access your user session (files, network) ' + 'beyond the extension manifest policy. Only continue if you trust ' + 'this extension package.', + ), + ], + ), + ), + actions: [ + OutlineButton( + onPressed: () => material.Navigator.pop(dialogContext, false), + child: const material.Text('Cancel'), + ), + PrimaryButton( + onPressed: () => material.Navigator.pop(dialogContext, true), + child: const material.Text('Run without OS sandbox'), + ), + ], + ), + ); + return approved == true; +} diff --git a/lib/features/main_screen/main_screen.dart b/lib/features/main_screen/main_screen.dart index 307fd937..f24f201e 100644 --- a/lib/features/main_screen/main_screen.dart +++ b/lib/features/main_screen/main_screen.dart @@ -13,6 +13,8 @@ import 'package:querya_desktop/core/storage/app_settings.dart'; import 'package:querya_desktop/core/storage/local_db.dart'; import 'package:querya_desktop/core/theme/querya_theme_scope.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:querya_desktop/core/extensions/sandbox/unsandboxed_launch_consent_gate.dart'; +import 'package:querya_desktop/features/extensions/presentation/widgets/unsandboxed_driver_consent_dialog.dart'; import 'package:querya_desktop/features/connections/connection_creation_flow.dart'; import 'package:querya_desktop/features/connections/new_connection_url_dialog.dart'; import 'package:querya_desktop/features/connections/connections_panel.dart'; @@ -39,8 +41,18 @@ class _MainScreenState extends State { final ValueNotifier _workspace = ValueNotifier(MainScreenWorkspaceState.empty); + @override + void initState() { + super.initState(); + UnsandboxedLaunchConsentGate.instance.handler = (details) { + if (!mounted) return Future.value(false); + return showUnsandboxedDriverConsentDialog(context, details); + }; + } + @override void dispose() { + UnsandboxedLaunchConsentGate.instance.handler = null; _workspace.dispose(); super.dispose(); } diff --git a/test/core/extensions/sandbox/sandbox_os_isolation_test.dart b/test/core/extensions/sandbox/sandbox_os_isolation_test.dart new file mode 100644 index 00000000..b0c7f401 --- /dev/null +++ b/test/core/extensions/sandbox/sandbox_os_isolation_test.dart @@ -0,0 +1,48 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:querya_desktop/core/extensions/sandbox/sandbox_launch_command.dart'; +import 'package:querya_desktop/core/extensions/sandbox/sandbox_os_isolation.dart'; + +void main() { + group('SandboxOsIsolation', () { + test('returns null when launch command uses OS sandbox', () { + final command = SandboxLaunchCommand.build( + pluginExecutable: '/bin/driver', + scratchPath: '/tmp/s', + platformOverride: 'linux', + bwrapAvailable: true, + ); + + expect( + SandboxOsIsolation.exceptionForLaunchCommand(command), + isNull, + ); + }); + + test('returns linux message when bwrap unavailable', () { + final command = SandboxLaunchCommand.build( + pluginExecutable: '/bin/driver', + scratchPath: '/tmp/s', + platformOverride: 'linux', + bwrapAvailable: false, + ); + + final error = SandboxOsIsolation.exceptionForLaunchCommand(command); + expect(error, isNotNull); + expect(error!.platform, 'linux'); + expect(error.message, contains('bubblewrap')); + }); + + test('returns windows message for soft isolation path', () { + final command = SandboxLaunchCommand.build( + pluginExecutable: r'C:\driver.exe', + scratchPath: r'C:\tmp\s', + platformOverride: 'windows', + ); + + final error = SandboxOsIsolation.exceptionForLaunchCommand(command); + expect(error, isNotNull); + expect(error!.platform, 'windows'); + expect(error.message, contains('Windows')); + }); + }); +} diff --git a/test/core/extensions/sandbox/sandbox_process_runner_test.dart b/test/core/extensions/sandbox/sandbox_process_runner_test.dart index b2211b71..3b94223c 100644 --- a/test/core/extensions/sandbox/sandbox_process_runner_test.dart +++ b/test/core/extensions/sandbox/sandbox_process_runner_test.dart @@ -6,6 +6,7 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:path/path.dart' as p; import 'package:querya_desktop/core/extensions/models/sandbox_capabilities.dart'; import 'package:querya_desktop/core/extensions/sandbox/sandbox_launch_command.dart'; +import 'package:querya_desktop/core/extensions/sandbox/sandbox_os_isolation.dart'; import 'package:querya_desktop/core/extensions/sandbox/sandbox_process_runner.dart'; import 'package:querya_desktop/core/extensions/sandbox/sandbox_scratch_directory.dart'; @@ -259,6 +260,58 @@ void main() { await handle.dispose(); }); + test('refuses unsandboxed launch without explicit consent', () async { + final runner = SandboxProcessRunner( + platformOverride: 'linux', + bwrapAvailable: false, + scratchBaseDirectory: tempBase, + processStarter: ( + exe, + args, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + ProcessStartMode mode = ProcessStartMode.normal, + }) async => + _FakeProcess(), + ); + + await expectLater( + () => runner.start( + pluginId: 'test.driver', + pluginExecutable: '/bin/driver', + ), + throwsA(isA()), + ); + }); + + test('allows unsandboxed launch when consent flag is set', () async { + final runner = SandboxProcessRunner( + platformOverride: 'windows', + scratchBaseDirectory: tempBase, + processStarter: ( + exe, + args, { + String? workingDirectory, + Map? environment, + bool includeParentEnvironment = true, + bool runInShell = false, + ProcessStartMode mode = ProcessStartMode.normal, + }) async => + _FakeProcess(), + ); + + final handle = await runner.start( + pluginId: 'win.driver', + pluginExecutable: 'driver.exe', + allowUnsandboxedLaunch: true, + ); + + expect(handle.launchCommand.usesOsSandbox, isFalse); + await handle.dispose(); + }); + test('start deletes scratch when process spawn fails', () async { Directory? observedScratch; final runner = SandboxProcessRunner( @@ -282,6 +335,7 @@ void main() { () => runner.start( pluginId: 'fail.driver', pluginExecutable: 'driver.exe', + allowUnsandboxedLaunch: true, ), throwsA(isA()), ); diff --git "a/\320\220\320\275\320\260\320\273\320\270\320\267 Fluid Morphing \320\230\320\275\321\202\320\265\321\200\321\204\320\265\320\271\321\201\320\276\320\262.txt" "b/\320\220\320\275\320\260\320\273\320\270\320\267 Fluid Morphing \320\230\320\275\321\202\320\265\321\200\321\204\320\265\320\271\321\201\320\276\320\262.txt" new file mode 100644 index 00000000..a416b964 --- /dev/null +++ "b/\320\220\320\275\320\260\320\273\320\270\320\267 Fluid Morphing \320\230\320\275\321\202\320\265\321\200\321\204\320\265\320\271\321\201\320\276\320\262.txt" @@ -0,0 +1,148 @@ +Архитектура и реализация Fluid Morphing интерфейсов: от концептуальной физики до системной интеграции Flutter и Rust в настольных приложениях баз данных +Эволюция пользовательских интерфейсов в программном обеспечении отражает непрерывное стремление инженеров и дизайнеров сократить когнитивную дистанцию между человеком и вычислительной системой. Исторически цифровые интерфейсы строились на парадигме дискретных, мгновенных переключений состояний, что было обусловлено аппаратными ограничениями. Однако по мере роста вычислительных мощностей индустрия переходит к непрерывным, физически обоснованным моделям взаимодействия. В авангарде этого перехода находится концепция «Fluid, morphing interface» (текучего, морфирующего интерфейса). Данное исследование представляет собой исчерпывающий анализ принципов построения таких интерфейсов, математических и архитектурных требований к их реализации, специфики применения в кроссплатформенной экосистеме Flutter, а также стратегической необходимости использования системных языков программирования, таких как Rust, для обеспечения бескомпромиссной производительности в ресурсоемких настольных приложениях — в частности, в контексте клиента баз данных Querya Desktop. +Концептуальные основы и фундаментальные требования к Fluid Interfaces +Концепция «Fluid Interfaces» (текучих интерфейсов) приобрела статус индустриального стандарта и была формализована в рамках знаковой презентации Apple на конференции WWDC 2018 («Designing Fluid Interfaces», Session 803)1. Основная философская и когнитивная предпосылка этой парадигмы заключается в том, что интерфейс должен ощущаться не как механизм, которым нужно управлять, а как естественное, бесшовное продолжение человеческого разума и физического тела1. +Когда интерфейс ведет себя в строгом соответствии с ментальной моделью физического мира, когнитивная нагрузка на пользователя радикально снижается6. В реальном мире объекты не телепортируются в пространстве, не останавливаются мгновенно при потере контакта и обладают характеристиками массы, инерции, трения и упругости8. Симуляция этих характеристик в цифровой среде требует соблюдения трех фундаментальных архитектурных требований: мгновенного отклика (Responsive), прерываемости (Interruptible) и перенаправляемости (Redirectable)5. +Мгновенный отклик и прямое манипулирование (Responsiveness & Direct Manipulation) +В физическом мире реакция на прикосновение к объекту происходит мгновенно. В цифровой среде появление даже микроскопической задержки (latency) разрушает когнитивную иллюзию прямого манипулирования (direct manipulation)8. Отклик является фундаментом, на котором строятся все остальные взаимодействия. +Интерфейс обязан реагировать на событие первоначального касания или нажатия (pointer-down), а не дожидаться момента отпускания манипулятора (pointer-up или click)8. Ожидание полного цикла клика для визуализации обратной связи делает систему субъективно "мертвой". Отслеживание движений пальца или курсора должно происходить в строгом соотношении 1:1, при этом критически важно сохранять изначальное смещение (grab offset) между точкой касания и геометрическим центром объекта6. Принудительное примагничивание центра элемента к курсору в момент захвата нарушает пространственную целостность восприятия. +Архитектурно это требует захвата указателя (pointer capture), чтобы отслеживание продолжалось даже в том случае, если курсор выходит за пределы изначальных границ элемента8. Любые искусственные таймеры, функции антидребезга (debounce) или задержки перед началом визуального отклика строго недопустимы, поскольку они создают барьер между намерением пользователя и реакцией системы5. +Абсолютная прерываемость (Interruptibility) +Прерываемость является наиболее сложным в реализации, но критически важным требованием. Человеческая мысль и физический жест происходят параллельно; намерение может измениться в любую долю секунды5. Традиционная анимация, основанная на жестко заданном времени (например, CSS-переходы transition или @keyframes), концептуально непригодна для текучих интерфейсов, так как она не позволяет бесшовно перехватить объект в полете6. +Если пользователь смахивает панель, а затем, осознав ошибку, пытается поймать ее на середине экрана, система обязана: +1. Мгновенно остановить текущую анимацию без визуальных скачков. +2. Считать текущее визуальное значение с экрана (presentation layer), а не конечное логическое значение. +3. Передать управление обратно жесту пользователя, сохранив при этом остаточную скорость объекта6. +Блокировка пользовательского ввода (input lockouts) во время транзитных состояний интерфейса является антипаттерном. Интерфейс должен оставаться функциональным и готовым к взаимодействию даже во время выполнения самых сложных анимационных последовательностей5. +Перенаправляемость и сохранение импульса (Redirectability) +Перенаправляемость описывает способность системы плавно менять конечную цель движения «на лету» без визуальных разрывов (seams) и скачков8. Если в процессе анимации закрытия модального окна поступает команда на его повторное открытие, система не должна мгновенно инвертировать вектор скорости (что вызовет эффект удара о невидимую стену — "brick wall effect"). Вместо этого вычисляется новая траектория, которая органично сливается с текущим вектором движения путем смешивания скоростей (velocity blending)8. +Физика взаимодействия: математика пружин и кинематика UI +Для реализации вышеописанных требований индустрия интерфейсов полностью отказывается от традиционных кривых плавности (easing curves), таких как ease-in или ease-out, в пользу систем, основанных на симуляции физики пружин (spring physics). +Отказ от концепции «продолжительности» +В парадигме текучих интерфейсов разработчики не оперируют фиксированным временем анимации (duration). Теоретически, физическая пружина никогда не останавливается полностью, а лишь асимптотически приближается к состоянию покоя. Принудительное навязывание продолжительности ломает физическую достоверность движения5. Вместо этого поведение системы описывается двумя ключевыми параметрами: коэффициентом затухания (Damping Ratio) и временем реакции (Response)8. + + +Параметр физической модели + Математическое описание и влияние на UX + Рекомендованные значения + Damping Ratio (Коэффициент затухания) + Определяет уровень трения в системе и контролирует осцилляцию (перелет). Значение означает критическое затухание (critically damped) — система достигает цели максимально быстро без отскока. Значения создают упругость и осцилляцию (bounciness). + + для большинства стандартных перемещений; для инерционных бросков, шторок и элементов, несущих физический импульс5. + Response (Время реакции) + Определяет жесткость пружины, выраженную через примерное время в секундах, необходимое для завершения основного пути движения. Меньшие значения делают интерфейс более "жестким" и резким. + + секунды для свайпов и открытия модальных окон, для свободного перемещения элементов (например, Picture-in-Picture)6. + Передача скорости (Velocity Handoff) +Точка, в которой заканчивается жест пользователя (pointer release) и начинается программная анимация, называется швом (seam). Для создания иллюзии непрерывности анимация должна унаследовать точную скорость жеста в момент отпускания8. В математическом выражении, начальная скорость пружины нормализуется относительно оставшегося расстояния до цели: + + +где — относительная скорость, передаваемая в алгоритм пружины, а — физическая скорость курсора или пальца в момент отпускания экрана (вычисляемая на основе истории последних событий pointermove)5. Если палец двигался со скоростью 50 пикселей в секунду, а до цели осталось 100 пикселей, начальная скорость пружины составит 8. +Проекция импульса (Momentum Projection) +При инерционном смахивании (flick) интерфейс должен вычислить, где объект остановился бы естественным образом под воздействием трения, и лишь затем выбрать ближайшую логическую точку привязки (snap target) для завершения движения6. Инженеры Apple используют для этого формулу экспоненциального затухания, которая отличается от классической ньютоновской кинематики: + + +где — начальная скорость жеста (initial velocity), а — коэффициент замедления (deceleration rate, часто принимаемый за константу для нормального прокручивания)3. Вычисленная проекция прибавляется к текущей позиции объекта, после чего система находит ближайшую валидную позицию (например, угол экрана для видеоплеера или край страницы) и перенаправляет пружину в эту точку3. +Декомпозиция сложного двумерного движения (например, перемещения плавающего окна) требует независимого расчета пружин по осям X и Y. Использование единой пружины для 2D-вектора приводит к десинхронизации, если начальные скорости по осям различаются8. +Анатомия Morphing-интерфейсов +В то время как принципы Fluid касаются пространственной динамики и кинематики, Morphing (морфинг) описывает плавную трансформацию геометрической формы, структуры, цвета или свойств объекта при переходе между логическими состояниями. В пользовательских интерфейсах морфинг чаще всего применяется к интерактивным элементам: плавающая кнопка действия (FAB) трансформируется в полноэкранное диалоговое окно, иконка «гамбургер» превращается в крестик закрытия, а карточка данных бесшовно раскрывается в детальное представление12. +Математическая и алгоритмическая сложность морфинга заключается в интерполяции между двумя геометрически различными фигурами (например, превращение звезды в круг или треугольника в квадрат). Прямолинейная интерполяция вершин одного контура в вершины другого неизбежно приводит к визуальным артефактам, таким как самопересечение формы (self-intersection) или неестественное вращение фигуры вокруг своей оси15. +В масштабных приложениях качественный морфинг достигается за счет сложных подготовительных алгоритмов: +1. Выравнивание контрольных точек (Point Alignment): Перед началом анимации алгоритм вычисляет оптимальное смещение (minimum shift) индексов массивов контрольных точек обеих фигур. Цель состоит в минимизации суммарного вектора перемещения всех точек. Это предотвращает "скручивание" фигуры во время трансформации15. +2. Унификация контуров (Contour Normalization): Обе фигуры должны быть приведены к одинаковому количеству сегментов (кривых Безье или дискретных точек). Прямые линии фигуры-источника динамически делятся на множество мелких кривых Безье, чтобы соответствовать сложным изгибам фигуры-цели. Однако топологические ограничения сохраняются: алгоритмы на базе метрик контуров могут трансформировать формы только с равным количеством замкнутых контуров (нельзя напрямую морфировать сплошной квадрат в фигуру с отверстием, например, букву "О")16. +3. Сохранение вершинной симметрии: При трансформации фигур с малым количеством углов (например, треугольник в прямоугольник) математическая интерполяция должна сохранять угловые вершины неподвижными, изгибая лишь необходимые грани (например, самая длинная грань треугольника изгибается, формируя две стороны прямоугольника). Использование сотен контрольных точек в таких случаях неэффективно и вызывает визуальные искажения ("дрожание" прямых линий)15. +Архитектура и реализация во фреймворке Flutter +Фреймворк Flutter, благодаря своему высокопроизводительному движку рендеринга (Skia и новое поколение Impeller), декларативной природе дерева виджетов и прямому доступу к низкоуровневым графическим примитивам, является превосходной платформой для создания текучих морфирующих интерфейсов. Анимации во Flutter жестко привязаны к частоте обновления экрана (Vsync), что позволяет напрямую управлять физическими параметрами на уровне каждого кадра7. +Инфраструктура виджетов и специализированные пакеты +Для решения задач морфинга в экосистеме Flutter разработаны продвинутые библиотеки, абстрагирующие сложную математику кривых Безье от разработчика прикладного слоя. +1. Пакет morphable_shape +Архитектура самого Flutter предоставляет базовый класс ShapeBorder, который накладывает жесткие ограничения: невозможно независимо стилизовать каждую сторону фигуры (как это реализовано в CSS) или плавно интерполировать сложные многоугольники18. Пакет morphable_shape полностью переписывает механизм работы с формами15. Особенности реализации: +* Динамические радиусы и процентные величины: Пакет интегрирует модуль dimension, позволяя задавать радиусы скругления и размеры не только в абсолютных пикселях, но и в процентах от размера родительского контейнера (например, DynamicRadius.elliptical(60.toPXLength, 10.toPercentLength)). Система автоматически масштабирует смежные радиусы, предотвращая их перекрытие19. +* Алгоритм симметричного морфинга: В отличие от базовых подходов, алгоритм морфинга здесь учитывает симметрию фигур. Он вычисляет контрольные точки лишь однажды, кэшируя результаты (если размер контейнера неизменен). Это позволяет изменять толщину границ, градиентную заливку и геометрическую форму без постоянного пересчета кривых Безье на каждом кадре15. +* Сериализация (JSON): Все объекты форм поддерживают методы toJson() и десериализацию. Это позволяет масштабным приложениям хранить описания сложных UI-состояний в базах данных или конфигурационных файлах и динамически загружать их18. +2. Пакет path_morph +Утилита, сфокусированная исключительно на интерполяции низкоуровневых объектов Path (путей)16. Алгоритм берет исходный путь и путь назначения, семплирует их через системный класс PathMetric (разбивая путь на равные отрезки) и математически сдвигает точки одного пути к другому15. Этот пакет требует от инженера ручного контроля над стейтом через AnimationController и setState, обновляя координаты массива shiftedPoints16. Как упоминалось ранее, его фундаментальным ограничением является требование равного количества контуров в обеих фигурах16. +3. Пакет flutter_morphing_icons +Инструмент прикладного уровня, предназначенный для трансформации иконок. Пакет реализует плавные переходы между состояниями (States) с применением встроенных кривых замедления (easing curves) и пространственных матричных трансформаций14. Он предоставляет абстракции, такие как MorphingAnimationConfig.scale или MorphingAnimationConfig.rotate, позволяя комбинировать кросс-фейд (исчезновение) с изменением масштаба и угла поворота, что создает иллюзию морфинга без тяжелых вычислений контуров векторной графики14. +Продвинутые техники: CustomPainter и фрагментные шейдеры +В высоконагруженных и визуально сложных приложениях стандартного дерева виджетов бывает недостаточно. Для создания органического морфинга (например, эффекта сливающихся капель жидкости — metaballs) математического смещения вершин фигур не хватает. +Для достижения таких эффектов инженеры Flutter используют CustomPainter в связке с фрагментными шейдерами (Fragment Shaders). С версии 3.7 Flutter поддерживает выполнение пользовательских шейдеров, написанных на языке GLSL и скомпилированных в SPIR-V17. Вместо просчета геометрии на CPU, шейдер выполняется параллельно на тысячах ядер GPU. Пространство описывается через функции расстояния со знаком (SDF - Signed Distance Field), а морфинг двух объектов реализуется с помощью математической функции сглаженного минимума (smooth polynomial minimum). Этот подход обеспечивает идеальную текучесть жидкости (fluid liquid effects) при практически нулевой нагрузке на центральный процессор17. +Уязвимости, узкие места и проблемы производительности (Jank) +Несмотря на высочайшие эстетические и когнитивные преимущества, внедрение Fluid Morphing интерфейсов привносит значительные архитектурные риски. Попытки реализовать подобные системы без глубокого понимания цикла рендеринга приводят к катастрофическому падению производительности. +Вычислительные затраты CPU и потеря кадров +Анимации, основанные на физике пружин и пересчете точек Безье для морфинга, требуют непрерывных вычислений на каждом кадре (16.6 мс для 60 FPS или 8.3 мс для 120 FPS дисплеев). Если эти вычисления происходят в главном потоке пользовательского интерфейса (UI Isolate во Flutter), они напрямую конкурируют за процессорное время с бизнес-логикой приложения17. Превышение временного лимита вызывает пропуск кадра (Jank), что мгновенно разрушает иллюзию физической достоверности и плавности8. Использование AnimatedBuilder с частым вызовом setState может приводить к инвалидации избыточно больших сегментов дерева виджетов, вызывая каскадные перерисовки15. +Управление слоями отрисовки (RepaintBoundary) +При морфинге формы контейнера система рендеринга Flutter по умолчанию перерисовывает не только саму форму, но и ее фон, тени, а также все дочерние элементы. Неконтролируемая перерисовка (repainting) сложных иерархий перегружает этап растрирования графического конвейера. Инженерным решением является изоляция морфирующего виджета с помощью RepaintBoundary, что принудительно выделяет элемент в отдельный слой (layer) кэширования17. Однако избыточное использование слоев приводит к исчерпанию оперативной памяти (особенно видеопамяти), что требует тонкой балансировки. +Конфликт с обработкой больших наборов данных +Наиболее уязвимым местом архитектуры текучих интерфейсов является их несовместимость с синхронной обработкой больших объемов данных. Например, в приложениях, работающих с тяжелыми аналитическими выборками, стандартный виджет DataTable во Flutter пытается вычислить внутренние размеры и отрендерить все переданные строки одновременно. При загрузке крупного датасета (large dataset) это полностью блокирует UI-поток, делая интерфейс "мертвым" и неспособным реагировать на жесты пользователя до завершения компоновки23. Отложенная загрузка (lazy loading) и разбиение вычислений на чанки частично решают проблему, но не устраняют корневой конфликт управляемых языков программирования. +Императив применения Rust в контексте Querya Desktop +Глубокий анализ ограничений производительности подводит нас к необходимости пересмотра архитектуры масштабных продуктов, оперирующих тяжелыми данными. Показательным примером применения системного подхода является проект Querya Desktop — разрабатываемый Владимиром Козловым (ZhuchkaTriplesix / QueryaHub) кроссплатформенный клиент для управления реляционными (SQL) и нереляционными (NoSQL) базами данных25. +Особенности доменной области (Database Client) +Querya Desktop позиционируется как легковесный клиент с фокусом на чистый интерфейс, работающий по принципу "local-first" (офлайн-обработка без зависимости от внешних облачных серверов)25. Специфика любого продвинутого клиента баз данных (подобного API Dash в сфере API-клиентов28) заключается в необходимости извлечения, парсинга, криптографической верификации и визуализации колоссальных объемов информации: многомегабайтных JSON-документов, десятков тысяч строк транзакционных таблиц и потоков бинарных данных28. +Конфликт сборщика мусора и текучего UX +Использование чистого Dart во Flutter для обработки таких массивов данных фатально для концепции Fluid UI. Dart работает в виртуальной машине со сборщиком мусора (Garbage Collector). Десериализация ответа СУБД (например, PostgreSQL или MongoDB) объемом 50 МБ непосредственно в объекты Dart приведет к мгновенной массовой аллокации сотен тысяч объектов в памяти. +Это неизбежно провоцирует остановки "Stop-The-World" (GC Pauses). Когда сборщик мусора приостанавливает выполнение изолята для сканирования и очистки кучи, UI-поток замирает. В этот момент любая физическая анимация пружины, перенаправление смахивания модального окна или морфинг FAB-кнопки будут заморожены30. Восприятие интерфейса как "продолжения разума" рушится. Кроме того, библиотеки для подключения к БД на чистом Dart исторически уступают в стабильности и скорости нативным драйверам. +Роль Rust и механизмов FFI +Внедрение системного языка Rust в архитектуру Querya Desktop не является избыточным усложнением (overengineering), а представляет собой жесткую техническую необходимость для защиты изолята пользовательского интерфейса. Rust компилируется в нативный машинный код, гарантирует безопасность памяти на этапе компиляции без использования сборщика мусора и обладает непревзойденной экосистемой высокопроизводительных I/O библиотек (Tokio, SQLx)30. +Архитектурное распределение обязанностей в такой системе выглядит следующим образом: + + +Уровень архитектуры + Технология + Ответственность и механизмы + Frontend (UI / UX Layer) + Flutter & Dart + Обработка Pointer Events, расчет физики пружин, морфинг форм (morphable_shape), рендеринг графики (Impeller). Задача — поддержание стабильных 120 FPS без блокировок7. + Bridge (Слой связи) + flutter_rust_bridge / rinf + Foreign Function Interface (FFI). Обеспечивает асинхронный вызов функций и потоковую передачу данных между языковыми средами33. + Backend (Core Engine) + Rust Core + Многопоточная обработка соединений (TCP/TLS), парсинг бинарных протоколов СУБД, фильтрация данных, криптография (интеграция опыта разработки OxyJWT25). + Технологии интеграции: flutter_rust_bridge vs rinf +Интеграция достигается посредством кодогенераторов, автоматизирующих создание безопасных привязок (bindings). +flutter_rust_bridge: Основное преимущество данного фреймворка в контексте десктопного приложения — поддержка абстракций с нулевым копированием (zero-copy)32. Когда ядро Rust получает из базы данных бинарный массив, оно может передать указатель на эту область памяти во Flutter. Вектор Vec из Rust проецируется во Flutter как Uint8List. Dart не копирует эти мегабайты в свою кучу, что полностью спасает Garbage Collector от перегрузки31. Кроме того, вызовы асинхронны: async fn в Rust выполняется в собственном пуле потоков операционной системы, оставляя UI-поток абсолютно свободным для рендеринга текучих анимаций32. +rinf: Является альтернативным архитектурным подходом. Если flutter_rust_bridge стремится скрыть границу между языками, заставляя функции Rust выглядеть как нативные функции Dart, то rinf использует явную систему обмена сообщениями (message passing). Этот подход избегает создания скрытых фоновых потоков моста, предоставляя разработчику тотальный контроль над асинхронными задачами (сотни тысяч конкурентных I/O операций), что делает архитектуру потоков данных более предсказуемой и прозрачной34. +Автор Querya Desktop, Владимир Козлов, имеет обширный опыт в Systems R&D, построении высококонкурентных архитектур и интеграции Rust с другими языками (проекты OxyJWT и высокоскоростной веб-фреймворк OxyRoute, обходящий GIL в Python)25. Использование ядра на Rust в Querya Desktop является логической кульминацией этого инженерного подхода: перенос "горячего пути" (hot path) вычислений в нативный код для максимизации пропускной способности при сохранении феноменального пользовательского опыта25. +Выводы и инженерные перспективы +Синтез данных о дизайне физических интерфейсов, графическом рендеринге и системном программировании позволяет сформулировать ключевые архитектурные выводы: +1. Конфликт парадигм и путь к симбиозу: Существует фундаментальный технический конфликт между потребностью человеческого восприятия в мгновенном, текучем интерфейсе (требующем жесткого окна в 8-16 миллисекунд на кадр) и необходимостью обработки масштабных массивов данных. Morphing-анимации, базирующиеся на пересчете сотен точек кривых Безье, многократно усиливают эту уязвимость. Без строгой архитектурной сегрегации эти два процесса — вычисления и рендеринг — взаимно уничтожают производительность системы. +2. Детерминизм против сборки мусора: Применение принципов Fluid UI в кроссплатформенной среде неизбежно обнажает ограничения управляемых языков при работе с тяжелым вводом-выводом. Визуальная отзывчивость не может быть гарантирована, если система подвержена недетерминированным паузам сборщика мусора. Паттерн "Flutter для UI + Rust для бизнес-логики" перестал быть просто экспериментальной оптимизацией. Для десктопных приложений корпоративного и профессионального класса (клиенты СУБД, IDE, тяжелые API-инструменты) этот симбиоз является технологическим императивом, обеспечивающим выживаемость концепции Fluid UI. +3. Эволюция программного обеспечения B2B и Developer Tools: Исторически инструменты для управления базами данных или серверами проектировались как утилитарные, статичные интерфейсы с таблицами, где задержки отклика считались приемлемой нормой. Внедрение Fluid Morphing интерфейсов в инструменты уровня Querya Desktop знаменует тектонический сдвиг. Информационные массивы перестают восприниматься как абстрактный текст; они обретают "физические" свойства, становясь осязаемыми объектами, которые можно свайпать, перенаправлять и интуитивно трансформировать. +Проектирование текучих морфирующих интерфейсов — это не поверхностная задача UI-дизайна. Это глубокая системная инженерная проблема, требующая бесшовной синхронизации между физикой анимации, механизмами аппаратного рендеринга и низкоуровневым управлением распределением памяти. Лишь интеграция мощных декларативных UI-фреймворков с бескомпромиссными системными языками способна обеспечить опыт взаимодействия, при котором граница между инструментом и человеческим разумом окончательно стирается. +Источники +1. Apple: “Designing Fluid Interfaces” – how to delight your mobile app users - 3Advance, https://objective-williamson3780.on.getshifter.io/posts/apple-designing-fluid-interfaces-techniques-to-delight-your-mobile-app-users/ +2. Natural gestures and animations inspired by Apple's WWDC18 talk "Designing Fluid Interfaces" - GitHub, https://github.com/nathangitter/fluid-interfaces +3. 803_Designing Fluid Interfaces-V4c_D_Final.key - Apple, https://devstreaming-cdn.apple.com/videos/wwdc/2018/803lpnlacvg2jsndx/803/803_designing_fluid_interfaces.pdf?dl=1 +4. Interfaces should be about feeling | by Nate Smith - UX Collective, https://uxdesign.cc/interfaces-should-be-about-feeling-11bda14ec20 +5. Building Fluid Interfaces. How to create natural gestures and… | by Nathan Gitter - Medium, https://medium.com/@nathangitter/building-fluid-interfaces-ios-swift-9732bb934bf5 +6. Build Fluid Interfaces with Skills for Design Engineers, https://surfskills.surf/s/emilkowalski/skills/apple-design +7. Animation & Transition | flutter package, https://flutter.ducafecat.com/en/pubs/animation-transition-packages +8. skills/skills/apple-design/SKILL.md at main · emilkowalski/skills - GitHub, https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md +9. Fluid Interfaces. At WWDC 2018 Apple introduced some… | by Mats Hauge - UX Collective, https://uxdesign.cc/fluid-interfaces-8302c95939fb +10. Making iOS animations feel just right | by Jille van der Weerd - Prototypr, https://blog.prototypr.io/making-animations-feel-just-right-673d797692c1 +11. Gestures in Fluid Interfaces — On Intent and Projection | by Christian Schnorr | iOS App Development | Medium, https://medium.com/ios-os-x-development/gestures-in-fluid-interfaces-on-intent-and-projection-36d158db7395 +12. Fluid Morph Background - Vengeance UI, https://www.vengenceui.com/components/fluid-morph-bg +13. Morphing Controls design pattern - UI-Patterns.com, https://ui-patterns.com/patterns/morphing-controls +14. flutter_morphing_icons | Flutter package - Pub.dev, https://pub.dev/packages/flutter_morphing_icons +15. Creating morphable shapes in Flutter - Wenkai Fan - Medium, https://kevinvan.medium.com/creating-morphable-shapes-in-flutter-f17bcfecb0ed +16. GitHub - ikbendewilliam/path_morph_for_flutter: This is a Flutter package that lets you smoothly morph one path into another., https://github.com/ikbendewilliam/path_morph_for_flutter +17. Effective Animations in Flutter - Plague Fox, https://plugfox.dev/effective-animations-in-flutter/ +18. Design Shapes in Flutter — Introduction to the Morphable Shape Package | by Wenkai Fan, https://itnext.io/design-shapes-in-flutter-introduction-to-the-morphable-shape-package-30e0d33c60a7 +19. morphable_shape | Flutter package - Pub.dev, https://pub.dev/packages/morphable_shape +20. morphable_shape 0.0.9 | Flutter package - pub.dev, https://pub.dev/packages/morphable_shape/versions/0.0.9 +21. path_morph - Flutter package in Animation & Transition category, https://fluttergems.dev/packages/path_morph/ +22. path_morph package - All Versions - Pub.dev, https://pub.dev/packages/path_morph/versions +23. How to Find Performance and Bottleneck Issues in Flutter Apps? - % - Mobikul, https://mobikul.com/find-performance-and-bottleneck-issue-in-flutter-apps/ +24. How can I optimize DataTable in Flutter for large datasets, similar to ListView.builder?, https://www.reddit.com/r/FlutterDev/comments/1k0w9ye/how_can_i_optimize_datatable_in_flutter_for_large/ +25. Eva Rei ZhuchkaTriplesix - GitHub, https://github.com/ZhuchkaTriplesix +26. Приложение «Querya - App Store - Apple, https://apps.apple.com/ru/app/querya/id6756846513 +27. Querya - App Store - Apple, https://apps.apple.com/it/app/querya/id6756846513 +28. GitHub - api-evangelist/api-dash: API Dash is a beautiful AI-powered open-source cross-platform API client built with Flutter, available on Desktop (macOS, Windows, Linux) and Mobile., https://github.com/api-evangelist/api-dash +29. Introducing API Dash - An open-source cross-platform API Client built using Flutter (https://github.com/foss42/api-dash) : r/FlutterDev - Reddit, https://www.reddit.com/r/FlutterDev/comments/123p41l/introducing_api_dash_an_opensource_crossplatform/ +30. Rust + Flutter: How to Build Fast, Safe, Cross-Platform Mobile Apps - DEV Community, https://dev.to/abibeh/rust-flutter-how-to-build-fast-safe-cross-platform-mobile-apps-ika +31. Show HN: Rust GUI Library via Flutter | Hacker News, https://news.ycombinator.com/item?id=41213711 +32. flutter_rust_bridge - crates.io: Rust Package Registry, https://crates.io/crates/flutter_rust_bridge/1.10.0 +33. Flutter Rust Bridge download | SourceForge.net, https://sourceforge.net/projects/flutter-rust-bridge.mirror/ +34. Rinf vs `flutter_rust_bridge` · cunarist rinf · Discussion #195 - GitHub, https://github.com/cunarist/rinf/discussions/195 +35. Flutter plus Rust is the same stack I'm using for my music app. Thanks to the aw... | Hacker News, https://news.ycombinator.com/item?id=41749424 \ No newline at end of file From a3f58dbc00597f4018ca8bded76602d899e00977 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Mon, 27 Jul 2026 11:19:36 +0300 Subject: [PATCH 2/4] chore: remove accidental local analysis file from branch --- ...0\265\320\271\321\201\320\276\320\262.txt" | 148 ------------------ 1 file changed, 148 deletions(-) delete mode 100644 "\320\220\320\275\320\260\320\273\320\270\320\267 Fluid Morphing \320\230\320\275\321\202\320\265\321\200\321\204\320\265\320\271\321\201\320\276\320\262.txt" diff --git "a/\320\220\320\275\320\260\320\273\320\270\320\267 Fluid Morphing \320\230\320\275\321\202\320\265\321\200\321\204\320\265\320\271\321\201\320\276\320\262.txt" "b/\320\220\320\275\320\260\320\273\320\270\320\267 Fluid Morphing \320\230\320\275\321\202\320\265\321\200\321\204\320\265\320\271\321\201\320\276\320\262.txt" deleted file mode 100644 index a416b964..00000000 --- "a/\320\220\320\275\320\260\320\273\320\270\320\267 Fluid Morphing \320\230\320\275\321\202\320\265\321\200\321\204\320\265\320\271\321\201\320\276\320\262.txt" +++ /dev/null @@ -1,148 +0,0 @@ -Архитектура и реализация Fluid Morphing интерфейсов: от концептуальной физики до системной интеграции Flutter и Rust в настольных приложениях баз данных -Эволюция пользовательских интерфейсов в программном обеспечении отражает непрерывное стремление инженеров и дизайнеров сократить когнитивную дистанцию между человеком и вычислительной системой. Исторически цифровые интерфейсы строились на парадигме дискретных, мгновенных переключений состояний, что было обусловлено аппаратными ограничениями. Однако по мере роста вычислительных мощностей индустрия переходит к непрерывным, физически обоснованным моделям взаимодействия. В авангарде этого перехода находится концепция «Fluid, morphing interface» (текучего, морфирующего интерфейса). Данное исследование представляет собой исчерпывающий анализ принципов построения таких интерфейсов, математических и архитектурных требований к их реализации, специфики применения в кроссплатформенной экосистеме Flutter, а также стратегической необходимости использования системных языков программирования, таких как Rust, для обеспечения бескомпромиссной производительности в ресурсоемких настольных приложениях — в частности, в контексте клиента баз данных Querya Desktop. -Концептуальные основы и фундаментальные требования к Fluid Interfaces -Концепция «Fluid Interfaces» (текучих интерфейсов) приобрела статус индустриального стандарта и была формализована в рамках знаковой презентации Apple на конференции WWDC 2018 («Designing Fluid Interfaces», Session 803)1. Основная философская и когнитивная предпосылка этой парадигмы заключается в том, что интерфейс должен ощущаться не как механизм, которым нужно управлять, а как естественное, бесшовное продолжение человеческого разума и физического тела1. -Когда интерфейс ведет себя в строгом соответствии с ментальной моделью физического мира, когнитивная нагрузка на пользователя радикально снижается6. В реальном мире объекты не телепортируются в пространстве, не останавливаются мгновенно при потере контакта и обладают характеристиками массы, инерции, трения и упругости8. Симуляция этих характеристик в цифровой среде требует соблюдения трех фундаментальных архитектурных требований: мгновенного отклика (Responsive), прерываемости (Interruptible) и перенаправляемости (Redirectable)5. -Мгновенный отклик и прямое манипулирование (Responsiveness & Direct Manipulation) -В физическом мире реакция на прикосновение к объекту происходит мгновенно. В цифровой среде появление даже микроскопической задержки (latency) разрушает когнитивную иллюзию прямого манипулирования (direct manipulation)8. Отклик является фундаментом, на котором строятся все остальные взаимодействия. -Интерфейс обязан реагировать на событие первоначального касания или нажатия (pointer-down), а не дожидаться момента отпускания манипулятора (pointer-up или click)8. Ожидание полного цикла клика для визуализации обратной связи делает систему субъективно "мертвой". Отслеживание движений пальца или курсора должно происходить в строгом соотношении 1:1, при этом критически важно сохранять изначальное смещение (grab offset) между точкой касания и геометрическим центром объекта6. Принудительное примагничивание центра элемента к курсору в момент захвата нарушает пространственную целостность восприятия. -Архитектурно это требует захвата указателя (pointer capture), чтобы отслеживание продолжалось даже в том случае, если курсор выходит за пределы изначальных границ элемента8. Любые искусственные таймеры, функции антидребезга (debounce) или задержки перед началом визуального отклика строго недопустимы, поскольку они создают барьер между намерением пользователя и реакцией системы5. -Абсолютная прерываемость (Interruptibility) -Прерываемость является наиболее сложным в реализации, но критически важным требованием. Человеческая мысль и физический жест происходят параллельно; намерение может измениться в любую долю секунды5. Традиционная анимация, основанная на жестко заданном времени (например, CSS-переходы transition или @keyframes), концептуально непригодна для текучих интерфейсов, так как она не позволяет бесшовно перехватить объект в полете6. -Если пользователь смахивает панель, а затем, осознав ошибку, пытается поймать ее на середине экрана, система обязана: -1. Мгновенно остановить текущую анимацию без визуальных скачков. -2. Считать текущее визуальное значение с экрана (presentation layer), а не конечное логическое значение. -3. Передать управление обратно жесту пользователя, сохранив при этом остаточную скорость объекта6. -Блокировка пользовательского ввода (input lockouts) во время транзитных состояний интерфейса является антипаттерном. Интерфейс должен оставаться функциональным и готовым к взаимодействию даже во время выполнения самых сложных анимационных последовательностей5. -Перенаправляемость и сохранение импульса (Redirectability) -Перенаправляемость описывает способность системы плавно менять конечную цель движения «на лету» без визуальных разрывов (seams) и скачков8. Если в процессе анимации закрытия модального окна поступает команда на его повторное открытие, система не должна мгновенно инвертировать вектор скорости (что вызовет эффект удара о невидимую стену — "brick wall effect"). Вместо этого вычисляется новая траектория, которая органично сливается с текущим вектором движения путем смешивания скоростей (velocity blending)8. -Физика взаимодействия: математика пружин и кинематика UI -Для реализации вышеописанных требований индустрия интерфейсов полностью отказывается от традиционных кривых плавности (easing curves), таких как ease-in или ease-out, в пользу систем, основанных на симуляции физики пружин (spring physics). -Отказ от концепции «продолжительности» -В парадигме текучих интерфейсов разработчики не оперируют фиксированным временем анимации (duration). Теоретически, физическая пружина никогда не останавливается полностью, а лишь асимптотически приближается к состоянию покоя. Принудительное навязывание продолжительности ломает физическую достоверность движения5. Вместо этого поведение системы описывается двумя ключевыми параметрами: коэффициентом затухания (Damping Ratio) и временем реакции (Response)8. - - -Параметр физической модели - Математическое описание и влияние на UX - Рекомендованные значения - Damping Ratio (Коэффициент затухания) - Определяет уровень трения в системе и контролирует осцилляцию (перелет). Значение означает критическое затухание (critically damped) — система достигает цели максимально быстро без отскока. Значения создают упругость и осцилляцию (bounciness). - - для большинства стандартных перемещений; для инерционных бросков, шторок и элементов, несущих физический импульс5. - Response (Время реакции) - Определяет жесткость пружины, выраженную через примерное время в секундах, необходимое для завершения основного пути движения. Меньшие значения делают интерфейс более "жестким" и резким. - - секунды для свайпов и открытия модальных окон, для свободного перемещения элементов (например, Picture-in-Picture)6. - Передача скорости (Velocity Handoff) -Точка, в которой заканчивается жест пользователя (pointer release) и начинается программная анимация, называется швом (seam). Для создания иллюзии непрерывности анимация должна унаследовать точную скорость жеста в момент отпускания8. В математическом выражении, начальная скорость пружины нормализуется относительно оставшегося расстояния до цели: - - -где — относительная скорость, передаваемая в алгоритм пружины, а — физическая скорость курсора или пальца в момент отпускания экрана (вычисляемая на основе истории последних событий pointermove)5. Если палец двигался со скоростью 50 пикселей в секунду, а до цели осталось 100 пикселей, начальная скорость пружины составит 8. -Проекция импульса (Momentum Projection) -При инерционном смахивании (flick) интерфейс должен вычислить, где объект остановился бы естественным образом под воздействием трения, и лишь затем выбрать ближайшую логическую точку привязки (snap target) для завершения движения6. Инженеры Apple используют для этого формулу экспоненциального затухания, которая отличается от классической ньютоновской кинематики: - - -где — начальная скорость жеста (initial velocity), а — коэффициент замедления (deceleration rate, часто принимаемый за константу для нормального прокручивания)3. Вычисленная проекция прибавляется к текущей позиции объекта, после чего система находит ближайшую валидную позицию (например, угол экрана для видеоплеера или край страницы) и перенаправляет пружину в эту точку3. -Декомпозиция сложного двумерного движения (например, перемещения плавающего окна) требует независимого расчета пружин по осям X и Y. Использование единой пружины для 2D-вектора приводит к десинхронизации, если начальные скорости по осям различаются8. -Анатомия Morphing-интерфейсов -В то время как принципы Fluid касаются пространственной динамики и кинематики, Morphing (морфинг) описывает плавную трансформацию геометрической формы, структуры, цвета или свойств объекта при переходе между логическими состояниями. В пользовательских интерфейсах морфинг чаще всего применяется к интерактивным элементам: плавающая кнопка действия (FAB) трансформируется в полноэкранное диалоговое окно, иконка «гамбургер» превращается в крестик закрытия, а карточка данных бесшовно раскрывается в детальное представление12. -Математическая и алгоритмическая сложность морфинга заключается в интерполяции между двумя геометрически различными фигурами (например, превращение звезды в круг или треугольника в квадрат). Прямолинейная интерполяция вершин одного контура в вершины другого неизбежно приводит к визуальным артефактам, таким как самопересечение формы (self-intersection) или неестественное вращение фигуры вокруг своей оси15. -В масштабных приложениях качественный морфинг достигается за счет сложных подготовительных алгоритмов: -1. Выравнивание контрольных точек (Point Alignment): Перед началом анимации алгоритм вычисляет оптимальное смещение (minimum shift) индексов массивов контрольных точек обеих фигур. Цель состоит в минимизации суммарного вектора перемещения всех точек. Это предотвращает "скручивание" фигуры во время трансформации15. -2. Унификация контуров (Contour Normalization): Обе фигуры должны быть приведены к одинаковому количеству сегментов (кривых Безье или дискретных точек). Прямые линии фигуры-источника динамически делятся на множество мелких кривых Безье, чтобы соответствовать сложным изгибам фигуры-цели. Однако топологические ограничения сохраняются: алгоритмы на базе метрик контуров могут трансформировать формы только с равным количеством замкнутых контуров (нельзя напрямую морфировать сплошной квадрат в фигуру с отверстием, например, букву "О")16. -3. Сохранение вершинной симметрии: При трансформации фигур с малым количеством углов (например, треугольник в прямоугольник) математическая интерполяция должна сохранять угловые вершины неподвижными, изгибая лишь необходимые грани (например, самая длинная грань треугольника изгибается, формируя две стороны прямоугольника). Использование сотен контрольных точек в таких случаях неэффективно и вызывает визуальные искажения ("дрожание" прямых линий)15. -Архитектура и реализация во фреймворке Flutter -Фреймворк Flutter, благодаря своему высокопроизводительному движку рендеринга (Skia и новое поколение Impeller), декларативной природе дерева виджетов и прямому доступу к низкоуровневым графическим примитивам, является превосходной платформой для создания текучих морфирующих интерфейсов. Анимации во Flutter жестко привязаны к частоте обновления экрана (Vsync), что позволяет напрямую управлять физическими параметрами на уровне каждого кадра7. -Инфраструктура виджетов и специализированные пакеты -Для решения задач морфинга в экосистеме Flutter разработаны продвинутые библиотеки, абстрагирующие сложную математику кривых Безье от разработчика прикладного слоя. -1. Пакет morphable_shape -Архитектура самого Flutter предоставляет базовый класс ShapeBorder, который накладывает жесткие ограничения: невозможно независимо стилизовать каждую сторону фигуры (как это реализовано в CSS) или плавно интерполировать сложные многоугольники18. Пакет morphable_shape полностью переписывает механизм работы с формами15. Особенности реализации: -* Динамические радиусы и процентные величины: Пакет интегрирует модуль dimension, позволяя задавать радиусы скругления и размеры не только в абсолютных пикселях, но и в процентах от размера родительского контейнера (например, DynamicRadius.elliptical(60.toPXLength, 10.toPercentLength)). Система автоматически масштабирует смежные радиусы, предотвращая их перекрытие19. -* Алгоритм симметричного морфинга: В отличие от базовых подходов, алгоритм морфинга здесь учитывает симметрию фигур. Он вычисляет контрольные точки лишь однажды, кэшируя результаты (если размер контейнера неизменен). Это позволяет изменять толщину границ, градиентную заливку и геометрическую форму без постоянного пересчета кривых Безье на каждом кадре15. -* Сериализация (JSON): Все объекты форм поддерживают методы toJson() и десериализацию. Это позволяет масштабным приложениям хранить описания сложных UI-состояний в базах данных или конфигурационных файлах и динамически загружать их18. -2. Пакет path_morph -Утилита, сфокусированная исключительно на интерполяции низкоуровневых объектов Path (путей)16. Алгоритм берет исходный путь и путь назначения, семплирует их через системный класс PathMetric (разбивая путь на равные отрезки) и математически сдвигает точки одного пути к другому15. Этот пакет требует от инженера ручного контроля над стейтом через AnimationController и setState, обновляя координаты массива shiftedPoints16. Как упоминалось ранее, его фундаментальным ограничением является требование равного количества контуров в обеих фигурах16. -3. Пакет flutter_morphing_icons -Инструмент прикладного уровня, предназначенный для трансформации иконок. Пакет реализует плавные переходы между состояниями (States) с применением встроенных кривых замедления (easing curves) и пространственных матричных трансформаций14. Он предоставляет абстракции, такие как MorphingAnimationConfig.scale или MorphingAnimationConfig.rotate, позволяя комбинировать кросс-фейд (исчезновение) с изменением масштаба и угла поворота, что создает иллюзию морфинга без тяжелых вычислений контуров векторной графики14. -Продвинутые техники: CustomPainter и фрагментные шейдеры -В высоконагруженных и визуально сложных приложениях стандартного дерева виджетов бывает недостаточно. Для создания органического морфинга (например, эффекта сливающихся капель жидкости — metaballs) математического смещения вершин фигур не хватает. -Для достижения таких эффектов инженеры Flutter используют CustomPainter в связке с фрагментными шейдерами (Fragment Shaders). С версии 3.7 Flutter поддерживает выполнение пользовательских шейдеров, написанных на языке GLSL и скомпилированных в SPIR-V17. Вместо просчета геометрии на CPU, шейдер выполняется параллельно на тысячах ядер GPU. Пространство описывается через функции расстояния со знаком (SDF - Signed Distance Field), а морфинг двух объектов реализуется с помощью математической функции сглаженного минимума (smooth polynomial minimum). Этот подход обеспечивает идеальную текучесть жидкости (fluid liquid effects) при практически нулевой нагрузке на центральный процессор17. -Уязвимости, узкие места и проблемы производительности (Jank) -Несмотря на высочайшие эстетические и когнитивные преимущества, внедрение Fluid Morphing интерфейсов привносит значительные архитектурные риски. Попытки реализовать подобные системы без глубокого понимания цикла рендеринга приводят к катастрофическому падению производительности. -Вычислительные затраты CPU и потеря кадров -Анимации, основанные на физике пружин и пересчете точек Безье для морфинга, требуют непрерывных вычислений на каждом кадре (16.6 мс для 60 FPS или 8.3 мс для 120 FPS дисплеев). Если эти вычисления происходят в главном потоке пользовательского интерфейса (UI Isolate во Flutter), они напрямую конкурируют за процессорное время с бизнес-логикой приложения17. Превышение временного лимита вызывает пропуск кадра (Jank), что мгновенно разрушает иллюзию физической достоверности и плавности8. Использование AnimatedBuilder с частым вызовом setState может приводить к инвалидации избыточно больших сегментов дерева виджетов, вызывая каскадные перерисовки15. -Управление слоями отрисовки (RepaintBoundary) -При морфинге формы контейнера система рендеринга Flutter по умолчанию перерисовывает не только саму форму, но и ее фон, тени, а также все дочерние элементы. Неконтролируемая перерисовка (repainting) сложных иерархий перегружает этап растрирования графического конвейера. Инженерным решением является изоляция морфирующего виджета с помощью RepaintBoundary, что принудительно выделяет элемент в отдельный слой (layer) кэширования17. Однако избыточное использование слоев приводит к исчерпанию оперативной памяти (особенно видеопамяти), что требует тонкой балансировки. -Конфликт с обработкой больших наборов данных -Наиболее уязвимым местом архитектуры текучих интерфейсов является их несовместимость с синхронной обработкой больших объемов данных. Например, в приложениях, работающих с тяжелыми аналитическими выборками, стандартный виджет DataTable во Flutter пытается вычислить внутренние размеры и отрендерить все переданные строки одновременно. При загрузке крупного датасета (large dataset) это полностью блокирует UI-поток, делая интерфейс "мертвым" и неспособным реагировать на жесты пользователя до завершения компоновки23. Отложенная загрузка (lazy loading) и разбиение вычислений на чанки частично решают проблему, но не устраняют корневой конфликт управляемых языков программирования. -Императив применения Rust в контексте Querya Desktop -Глубокий анализ ограничений производительности подводит нас к необходимости пересмотра архитектуры масштабных продуктов, оперирующих тяжелыми данными. Показательным примером применения системного подхода является проект Querya Desktop — разрабатываемый Владимиром Козловым (ZhuchkaTriplesix / QueryaHub) кроссплатформенный клиент для управления реляционными (SQL) и нереляционными (NoSQL) базами данных25. -Особенности доменной области (Database Client) -Querya Desktop позиционируется как легковесный клиент с фокусом на чистый интерфейс, работающий по принципу "local-first" (офлайн-обработка без зависимости от внешних облачных серверов)25. Специфика любого продвинутого клиента баз данных (подобного API Dash в сфере API-клиентов28) заключается в необходимости извлечения, парсинга, криптографической верификации и визуализации колоссальных объемов информации: многомегабайтных JSON-документов, десятков тысяч строк транзакционных таблиц и потоков бинарных данных28. -Конфликт сборщика мусора и текучего UX -Использование чистого Dart во Flutter для обработки таких массивов данных фатально для концепции Fluid UI. Dart работает в виртуальной машине со сборщиком мусора (Garbage Collector). Десериализация ответа СУБД (например, PostgreSQL или MongoDB) объемом 50 МБ непосредственно в объекты Dart приведет к мгновенной массовой аллокации сотен тысяч объектов в памяти. -Это неизбежно провоцирует остановки "Stop-The-World" (GC Pauses). Когда сборщик мусора приостанавливает выполнение изолята для сканирования и очистки кучи, UI-поток замирает. В этот момент любая физическая анимация пружины, перенаправление смахивания модального окна или морфинг FAB-кнопки будут заморожены30. Восприятие интерфейса как "продолжения разума" рушится. Кроме того, библиотеки для подключения к БД на чистом Dart исторически уступают в стабильности и скорости нативным драйверам. -Роль Rust и механизмов FFI -Внедрение системного языка Rust в архитектуру Querya Desktop не является избыточным усложнением (overengineering), а представляет собой жесткую техническую необходимость для защиты изолята пользовательского интерфейса. Rust компилируется в нативный машинный код, гарантирует безопасность памяти на этапе компиляции без использования сборщика мусора и обладает непревзойденной экосистемой высокопроизводительных I/O библиотек (Tokio, SQLx)30. -Архитектурное распределение обязанностей в такой системе выглядит следующим образом: - - -Уровень архитектуры - Технология - Ответственность и механизмы - Frontend (UI / UX Layer) - Flutter & Dart - Обработка Pointer Events, расчет физики пружин, морфинг форм (morphable_shape), рендеринг графики (Impeller). Задача — поддержание стабильных 120 FPS без блокировок7. - Bridge (Слой связи) - flutter_rust_bridge / rinf - Foreign Function Interface (FFI). Обеспечивает асинхронный вызов функций и потоковую передачу данных между языковыми средами33. - Backend (Core Engine) - Rust Core - Многопоточная обработка соединений (TCP/TLS), парсинг бинарных протоколов СУБД, фильтрация данных, криптография (интеграция опыта разработки OxyJWT25). - Технологии интеграции: flutter_rust_bridge vs rinf -Интеграция достигается посредством кодогенераторов, автоматизирующих создание безопасных привязок (bindings). -flutter_rust_bridge: Основное преимущество данного фреймворка в контексте десктопного приложения — поддержка абстракций с нулевым копированием (zero-copy)32. Когда ядро Rust получает из базы данных бинарный массив, оно может передать указатель на эту область памяти во Flutter. Вектор Vec из Rust проецируется во Flutter как Uint8List. Dart не копирует эти мегабайты в свою кучу, что полностью спасает Garbage Collector от перегрузки31. Кроме того, вызовы асинхронны: async fn в Rust выполняется в собственном пуле потоков операционной системы, оставляя UI-поток абсолютно свободным для рендеринга текучих анимаций32. -rinf: Является альтернативным архитектурным подходом. Если flutter_rust_bridge стремится скрыть границу между языками, заставляя функции Rust выглядеть как нативные функции Dart, то rinf использует явную систему обмена сообщениями (message passing). Этот подход избегает создания скрытых фоновых потоков моста, предоставляя разработчику тотальный контроль над асинхронными задачами (сотни тысяч конкурентных I/O операций), что делает архитектуру потоков данных более предсказуемой и прозрачной34. -Автор Querya Desktop, Владимир Козлов, имеет обширный опыт в Systems R&D, построении высококонкурентных архитектур и интеграции Rust с другими языками (проекты OxyJWT и высокоскоростной веб-фреймворк OxyRoute, обходящий GIL в Python)25. Использование ядра на Rust в Querya Desktop является логической кульминацией этого инженерного подхода: перенос "горячего пути" (hot path) вычислений в нативный код для максимизации пропускной способности при сохранении феноменального пользовательского опыта25. -Выводы и инженерные перспективы -Синтез данных о дизайне физических интерфейсов, графическом рендеринге и системном программировании позволяет сформулировать ключевые архитектурные выводы: -1. Конфликт парадигм и путь к симбиозу: Существует фундаментальный технический конфликт между потребностью человеческого восприятия в мгновенном, текучем интерфейсе (требующем жесткого окна в 8-16 миллисекунд на кадр) и необходимостью обработки масштабных массивов данных. Morphing-анимации, базирующиеся на пересчете сотен точек кривых Безье, многократно усиливают эту уязвимость. Без строгой архитектурной сегрегации эти два процесса — вычисления и рендеринг — взаимно уничтожают производительность системы. -2. Детерминизм против сборки мусора: Применение принципов Fluid UI в кроссплатформенной среде неизбежно обнажает ограничения управляемых языков при работе с тяжелым вводом-выводом. Визуальная отзывчивость не может быть гарантирована, если система подвержена недетерминированным паузам сборщика мусора. Паттерн "Flutter для UI + Rust для бизнес-логики" перестал быть просто экспериментальной оптимизацией. Для десктопных приложений корпоративного и профессионального класса (клиенты СУБД, IDE, тяжелые API-инструменты) этот симбиоз является технологическим императивом, обеспечивающим выживаемость концепции Fluid UI. -3. Эволюция программного обеспечения B2B и Developer Tools: Исторически инструменты для управления базами данных или серверами проектировались как утилитарные, статичные интерфейсы с таблицами, где задержки отклика считались приемлемой нормой. Внедрение Fluid Morphing интерфейсов в инструменты уровня Querya Desktop знаменует тектонический сдвиг. Информационные массивы перестают восприниматься как абстрактный текст; они обретают "физические" свойства, становясь осязаемыми объектами, которые можно свайпать, перенаправлять и интуитивно трансформировать. -Проектирование текучих морфирующих интерфейсов — это не поверхностная задача UI-дизайна. Это глубокая системная инженерная проблема, требующая бесшовной синхронизации между физикой анимации, механизмами аппаратного рендеринга и низкоуровневым управлением распределением памяти. Лишь интеграция мощных декларативных UI-фреймворков с бескомпромиссными системными языками способна обеспечить опыт взаимодействия, при котором граница между инструментом и человеческим разумом окончательно стирается. -Источники -1. Apple: “Designing Fluid Interfaces” – how to delight your mobile app users - 3Advance, https://objective-williamson3780.on.getshifter.io/posts/apple-designing-fluid-interfaces-techniques-to-delight-your-mobile-app-users/ -2. Natural gestures and animations inspired by Apple's WWDC18 talk "Designing Fluid Interfaces" - GitHub, https://github.com/nathangitter/fluid-interfaces -3. 803_Designing Fluid Interfaces-V4c_D_Final.key - Apple, https://devstreaming-cdn.apple.com/videos/wwdc/2018/803lpnlacvg2jsndx/803/803_designing_fluid_interfaces.pdf?dl=1 -4. Interfaces should be about feeling | by Nate Smith - UX Collective, https://uxdesign.cc/interfaces-should-be-about-feeling-11bda14ec20 -5. Building Fluid Interfaces. How to create natural gestures and… | by Nathan Gitter - Medium, https://medium.com/@nathangitter/building-fluid-interfaces-ios-swift-9732bb934bf5 -6. Build Fluid Interfaces with Skills for Design Engineers, https://surfskills.surf/s/emilkowalski/skills/apple-design -7. Animation & Transition | flutter package, https://flutter.ducafecat.com/en/pubs/animation-transition-packages -8. skills/skills/apple-design/SKILL.md at main · emilkowalski/skills - GitHub, https://github.com/emilkowalski/skills/blob/main/skills/apple-design/SKILL.md -9. Fluid Interfaces. At WWDC 2018 Apple introduced some… | by Mats Hauge - UX Collective, https://uxdesign.cc/fluid-interfaces-8302c95939fb -10. Making iOS animations feel just right | by Jille van der Weerd - Prototypr, https://blog.prototypr.io/making-animations-feel-just-right-673d797692c1 -11. Gestures in Fluid Interfaces — On Intent and Projection | by Christian Schnorr | iOS App Development | Medium, https://medium.com/ios-os-x-development/gestures-in-fluid-interfaces-on-intent-and-projection-36d158db7395 -12. Fluid Morph Background - Vengeance UI, https://www.vengenceui.com/components/fluid-morph-bg -13. Morphing Controls design pattern - UI-Patterns.com, https://ui-patterns.com/patterns/morphing-controls -14. flutter_morphing_icons | Flutter package - Pub.dev, https://pub.dev/packages/flutter_morphing_icons -15. Creating morphable shapes in Flutter - Wenkai Fan - Medium, https://kevinvan.medium.com/creating-morphable-shapes-in-flutter-f17bcfecb0ed -16. GitHub - ikbendewilliam/path_morph_for_flutter: This is a Flutter package that lets you smoothly morph one path into another., https://github.com/ikbendewilliam/path_morph_for_flutter -17. Effective Animations in Flutter - Plague Fox, https://plugfox.dev/effective-animations-in-flutter/ -18. Design Shapes in Flutter — Introduction to the Morphable Shape Package | by Wenkai Fan, https://itnext.io/design-shapes-in-flutter-introduction-to-the-morphable-shape-package-30e0d33c60a7 -19. morphable_shape | Flutter package - Pub.dev, https://pub.dev/packages/morphable_shape -20. morphable_shape 0.0.9 | Flutter package - pub.dev, https://pub.dev/packages/morphable_shape/versions/0.0.9 -21. path_morph - Flutter package in Animation & Transition category, https://fluttergems.dev/packages/path_morph/ -22. path_morph package - All Versions - Pub.dev, https://pub.dev/packages/path_morph/versions -23. How to Find Performance and Bottleneck Issues in Flutter Apps? - % - Mobikul, https://mobikul.com/find-performance-and-bottleneck-issue-in-flutter-apps/ -24. How can I optimize DataTable in Flutter for large datasets, similar to ListView.builder?, https://www.reddit.com/r/FlutterDev/comments/1k0w9ye/how_can_i_optimize_datatable_in_flutter_for_large/ -25. Eva Rei ZhuchkaTriplesix - GitHub, https://github.com/ZhuchkaTriplesix -26. Приложение «Querya - App Store - Apple, https://apps.apple.com/ru/app/querya/id6756846513 -27. Querya - App Store - Apple, https://apps.apple.com/it/app/querya/id6756846513 -28. GitHub - api-evangelist/api-dash: API Dash is a beautiful AI-powered open-source cross-platform API client built with Flutter, available on Desktop (macOS, Windows, Linux) and Mobile., https://github.com/api-evangelist/api-dash -29. Introducing API Dash - An open-source cross-platform API Client built using Flutter (https://github.com/foss42/api-dash) : r/FlutterDev - Reddit, https://www.reddit.com/r/FlutterDev/comments/123p41l/introducing_api_dash_an_opensource_crossplatform/ -30. Rust + Flutter: How to Build Fast, Safe, Cross-Platform Mobile Apps - DEV Community, https://dev.to/abibeh/rust-flutter-how-to-build-fast-safe-cross-platform-mobile-apps-ika -31. Show HN: Rust GUI Library via Flutter | Hacker News, https://news.ycombinator.com/item?id=41213711 -32. flutter_rust_bridge - crates.io: Rust Package Registry, https://crates.io/crates/flutter_rust_bridge/1.10.0 -33. Flutter Rust Bridge download | SourceForge.net, https://sourceforge.net/projects/flutter-rust-bridge.mirror/ -34. Rinf vs `flutter_rust_bridge` · cunarist rinf · Discussion #195 - GitHub, https://github.com/cunarist/rinf/discussions/195 -35. Flutter plus Rust is the same stack I'm using for my music app. Thanks to the aw... | Hacker News, https://news.ycombinator.com/item?id=41749424 \ No newline at end of file From 93f5cb52b4c400c542ae38c35dcd149ecb972b95 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Mon, 27 Jul 2026 11:39:59 +0300 Subject: [PATCH 3/4] test(sandbox): pass allowUnsandboxedLaunch in plugin RPC bridge tests Windows platformOverride in unit tests simulates soft isolation; consent flag is required after #395 fail-closed behavior. --- .../rpc/plugin_rpc_bridge_test.dart | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/test/core/extensions/rpc/plugin_rpc_bridge_test.dart b/test/core/extensions/rpc/plugin_rpc_bridge_test.dart index 6d5f73e9..c7bee5e8 100644 --- a/test/core/extensions/rpc/plugin_rpc_bridge_test.dart +++ b/test/core/extensions/rpc/plugin_rpc_bridge_test.dart @@ -150,6 +150,7 @@ void main() { final handshake = await bridge.start( manifest: testManifest, pluginExecutable: '/opt/driver', + allowUnsandboxedLaunch: true, ); expect(handshake, isA()); expect((handshake as Map)['protocolVersion'], '1.0'); @@ -190,7 +191,11 @@ void main() { ); await expectLater( - bridge.start(manifest: testManifest, pluginExecutable: '/opt/driver'), + bridge.start( + manifest: testManifest, + pluginExecutable: '/opt/driver', + allowUnsandboxedLaunch: true, + ), throwsA(isA()), ); expect(bridge.isStarted, isFalse); @@ -234,7 +239,11 @@ void main() { requestTimeout: const Duration(seconds: 5), ); - await bridge.start(manifest: testManifest, pluginExecutable: '/opt/driver'); + await bridge.start( + manifest: testManifest, + pluginExecutable: '/opt/driver', + allowUnsandboxedLaunch: true, + ); final pending = bridge.connect({'host': 'x'}); await Future.delayed(const Duration(milliseconds: 20)); process.completeExit(1); @@ -296,7 +305,11 @@ void main() { enableStderrPipe: false, ); - await bridge.start(manifest: testManifest, pluginExecutable: '/opt/driver'); + await bridge.start( + manifest: testManifest, + pluginExecutable: '/opt/driver', + allowUnsandboxedLaunch: true, + ); await bridge.shutdown(); expect(shutdownReceived, isTrue); From bd8acf1e37a6fc25c15aefb43ff5091e5b952344 Mon Sep 17 00:00:00 2001 From: ZhuchkaTriplesix Date: Mon, 27 Jul 2026 11:42:19 +0300 Subject: [PATCH 4/4] style(sandbox): use rethrow when consent is denied --- lib/core/extensions/extension_driver_session.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/core/extensions/extension_driver_session.dart b/lib/core/extensions/extension_driver_session.dart index 2c22e87f..ee8a3d7a 100644 --- a/lib/core/extensions/extension_driver_session.dart +++ b/lib/core/extensions/extension_driver_session.dart @@ -158,7 +158,7 @@ class ExtensionDriverSession { } on SandboxOsIsolationUnavailableException catch (error) { final approved = await UnsandboxedLaunchConsentGate.instance.request(error); - if (!approved) throw error; + if (!approved) rethrow; await bridge.start( manifest: manifest, pluginExecutable: pluginExecutable,