From 13c963575dae81943baf0c599a3bef840663839e Mon Sep 17 00:00:00 2001 From: jean-philippe Date: Sun, 26 Oct 2025 09:17:24 -0400 Subject: [PATCH] Comprehensive codebase audit and cleanup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fixed broken test suite: replaced counter widget tests with Console UI tests - Removed dead code: deleted unused MyHomePage widget and svrnty_components.dart library - Updated project branding: renamed package from "my_app" to "console" - Enhanced documentation: rewrote README with project features and setup instructions - Added coding standards: strict typing rules forbidding 'any' type across all languages - Implemented response protocol: structured answer format with context persistence - Fixed backend button: corrected URL from https to http, added proper error handling - Improved .gitignore: added Flutter plugins, CocoaPods, and design folder exclusions - Fixed UI overflow: increased status card height to prevent RenderFlex overflow These changes eliminate technical debt, establish code quality standards, and ensure all functionality works correctly across platforms. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .claude_docs/response-protocol.md | 99 ++++++ .gitignore | 8 + README.md | 79 ++++- claude.md | 139 ++++++++ ios/Podfile | 43 +++ lib/components/svrnty_components.dart | 478 -------------------------- lib/console_landing_page.dart | 47 ++- lib/main.dart | 86 ----- macos/Podfile | 42 +++ pubspec.yaml | 5 +- test/widget_test.dart | 27 +- 11 files changed, 454 insertions(+), 599 deletions(-) create mode 100644 .claude_docs/response-protocol.md create mode 100644 claude.md create mode 100644 ios/Podfile delete mode 100644 lib/components/svrnty_components.dart create mode 100644 macos/Podfile diff --git a/.claude_docs/response-protocol.md b/.claude_docs/response-protocol.md new file mode 100644 index 0000000..d540eb8 --- /dev/null +++ b/.claude_docs/response-protocol.md @@ -0,0 +1,99 @@ +# MANDATORY RESPONSE PROTOCOL + +**Claude must strictly follow this protocol for ALL responses in this project.** + +--- + +## πŸ—£οΈ Response Protocol β€” Defined Answer Types + +Claude must **always** end responses with exactly one of these two structured formats: + +--- + +### **Answer Type 1: Binary Choice** +Used for: simple confirmations, proceed/cancel actions, file operations. + +**Format:** + +(Y) Yes β€” [brief action summary] + +(N) No β€” [brief alternative/reason] + +(+) I don't understand β€” ask for clarification + + +**When user selects `(+)`:** +Claude responds: +> "What part would you like me to explain?" +Then teaches the concept step‑by‑step in plain language. + +--- + +### **Answer Type 2: Multiple Choice** +Used for: technical decisions, feature options, configuration paths. + +**Format:** + +(A) Option A β€” [minimalist description] + +(B) Option B β€” [minimalist description] + +(C) Option C β€” [minimalist description] + +(D) Option D β€” [minimalist description] + +(+) I don't understand β€” ask for clarification + + +**When user selects `(+)`:** +Claude responds: +> "Which option would you like explained, or should I clarify what we're deciding here?" +Then provides context on the decision + explains each option's purpose. + +--- + +### ⚠️ Mandatory Rules +1. **No text after the last option** β€” choices must be the final content. +2. Every option description ≀8 words. +3. The `(+)` option is **always present** in both formats. +4. When `(+)` is chosen, Claude shifts to teaching mode before re‑presenting options. +5. Claude must include `(always read claude.md to keep context between interactions)` before every option set. + +--- + +### Example 1 (Binary) + +We need to initialize npm in your project folder. + +(always read claude.md to keep context between interactions) + +(Y) Yes β€” run npm init -y now + +(N) No β€” show me what this does first + +(+) I don't understand β€” explain npm initialization + + +### Example 2 (Multiple Choice) + +Choose your testing framework: + +(always read claude.md to keep context between interactions) + +(A) Jest β€” popular, feature-rich + +(B) Vitest β€” faster, Vite-native + +(C) Node test runner β€” built-in, minimal + +(D) Skip tests β€” add later + +(+) I don't understand β€” explain testing frameworks + + +--- + +**This protocol ensures:** +- You always have an escape hatch to learn. +- Claude never assumes your technical knowledge. +- Every interaction has clear, actionable paths. diff --git a/.gitignore b/.gitignore index 3820a95..f1cd7f5 100644 --- a/.gitignore +++ b/.gitignore @@ -27,12 +27,17 @@ migrate_working_dir/ **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ +.flutter-plugins .flutter-plugins-dependencies .pub-cache/ .pub/ /build/ /coverage/ +# CocoaPods +**/Pods/ +**/Podfile.lock + # Symbolication related app.*.symbols @@ -43,3 +48,6 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +# Design/Documentation folders with duplicate assets +Svrnty_norms_guide/ diff --git a/README.md b/README.md index 103dec2..ffa28bf 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,77 @@ -# my_app +# Svrnty Console -A new Flutter project. +**Sovereign AI Solutions - Control Panel** + +A Flutter-based management console for the Svrnty AI platform, providing a modern interface for monitoring, configuring, and controlling AI agents and infrastructure. + +## Features + +- **Dashboard**: Real-time status monitoring of backend services, agents, and system health +- **The Architech**: AI infrastructure design and visualization (coming soon) +- **Agent Management**: Configure and monitor AI agents +- **Analytics**: Metrics and performance monitoring +- **Dark Theme**: Professional dark mode with Svrnty brand colors + +## Tech Stack + +- **Flutter 3.x** - Cross-platform UI framework +- **GetWidget** - Modern UI component library +- **Iconsax** - Clean, modern icon set +- **Animate Do** - Smooth animations +- **Custom Theming** - Svrnty brand colors (Crimson Red #C44D58, Slate Blue #475C6C) + +## Project Structure + +``` +lib/ +β”œβ”€β”€ main.dart # App entry point +β”œβ”€β”€ console_landing_page.dart # Main console UI +β”œβ”€β”€ theme.dart # Material theme configuration +β”œβ”€β”€ components/ +β”‚ └── navigation_sidebar.dart # Collapsible navigation +└── pages/ + └── architech_page.dart # The Architech module +``` ## Getting Started -This project is a starting point for a Flutter application. +### Prerequisites -A few resources to get you started if this is your first Flutter project: +- Flutter SDK 3.9.2 or higher +- Dart SDK 3.9.2 or higher -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) +### Installation -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +```bash +# Clone the repository +git clone [repository-url] +cd Console + +# Install dependencies +flutter pub get + +# Run the application +flutter run +``` + +### Development + +```bash +# Run tests +flutter test + +# Analyze code +flutter analyze + +# Build for production +flutter build macos # or ios, web, etc. +``` + +## Brand Fonts + +- **Montserrat** - Primary UI font +- **IBM Plex Mono** - Code and technical content + +## License + +Private - Svrnty AI Solutions diff --git a/claude.md b/claude.md new file mode 100644 index 0000000..4df6b09 --- /dev/null +++ b/claude.md @@ -0,0 +1,139 @@ +# πŸ”’ MANDATORY CODING STANDARDS + +## Strict Typing - NO EXCEPTIONS + +**Claude must ALWAYS use explicit types in ALL code. The use of `any` type is FORBIDDEN.** + +### Rules: +1. **Every variable must have an explicit type annotation** +2. **Every function parameter must be typed** +3. **Every function return value must be typed** +4. **Never use `any`, `dynamic` (in Dart), or equivalent loose types** +5. **Use proper generics, interfaces, and type unions instead** + +### Examples: + +❌ **FORBIDDEN:** +```typescript +const data: any = fetchData(); +function process(input: any): any { ... } +``` + +```dart +dynamic value = getValue(); +void handleData(var data) { ... } +``` + +βœ… **REQUIRED:** +```typescript +const data: UserData = fetchData(); +function process(input: UserInput): ProcessedOutput { ... } +``` + +```dart +UserData value = getValue(); +void handleData(RequestData data) { ... } +``` + +**This rule applies to:** +- TypeScript/JavaScript +- Dart/Flutter +- Python (use type hints) +- All statically-typed languages +- Even when interfacing with external APIs - create proper type definitions + +--- + +## πŸ—£οΈ Response Protocol β€” Defined Answer Types + +Claude must **always** end responses with exactly one of these two structured formats: + +--- + +### **Answer Type 1: Binary Choice** +Used for: simple confirmations, proceed/cancel actions, file operations. + +**Format:** + +(Y) Yes β€” [brief action summary] + +(N) No β€” [brief alternative/reason] + +(+) I don't understand β€” ask for clarification + + +**When user selects `(+)`:** +Claude responds: +> "What part would you like me to explain?" +Then teaches the concept step‑by‑step in plain language. + +--- + +### **Answer Type 2: Multiple Choice** +Used for: technical decisions, feature options, configuration paths. + +**Format:** + +(A) Option A β€” [minimalist description] + +(B) Option B β€” [minimalist description] + +(C) Option C β€” [minimalist description] + +(D) Option D β€” [minimalist description] + +(+) I don't understand β€” ask for clarification + + +**When user selects `(+)`:** +Claude responds: +> "Which option would you like explained, or should I clarify what we're deciding here?" +Then provides context on the decision + explains each option's purpose. + +--- + +### ⚠️ Mandatory Rules +1. **No text after the last option** β€” choices must be the final content. +2. Every option description ≀8 words. +3. The `(+)` option is **always present** in both formats. +4. When `(+)` is chosen, Claude shifts to teaching mode before re‑presenting options. +5. Claude must include `(always read claude.md to keep context between interactions)` before every option set. + +--- + +### Example 1 (Binary) + +We need to initialize npm in your project folder. + +(always read claude.md to keep context between interactions) + +(Y) Yes β€” run npm init -y now + +(N) No β€” show me what this does first + +(+) I don't understand β€” explain npm initialization + + +### Example 2 (Multiple Choice) + +Choose your testing framework: + +(always read claude.md to keep context between interactions) + +(A) Jest β€” popular, feature-rich + +(B) Vitest β€” faster, Vite-native + +(C) Node test runner β€” built-in, minimal + +(D) Skip tests β€” add later + +(+) I don't understand β€” explain testing frameworks + + +--- + +**This protocol ensures:** +- You always have an escape hatch to learn. +- Claude never assumes your technical knowledge. +- Every interaction has clear, actionable paths. diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..620e46e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/lib/components/svrnty_components.dart b/lib/components/svrnty_components.dart deleted file mode 100644 index f51e364..0000000 --- a/lib/components/svrnty_components.dart +++ /dev/null @@ -1,478 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:animate_do/animate_do.dart'; - -/// Svrnty Design System Components -/// Reusable, branded components for the Svrnty Console application -/// -/// Brand Colors: -/// - Primary (Crimson): #C44D58 -/// - Secondary (Slate Blue): #475C6C - -// ============================================================================ -// SVRNTY BUTTONS -// ============================================================================ - -enum SvrntyButtonVariant { primary, secondary, ghost, danger } - -class SvrntyButton extends StatelessWidget { - final String text; - final VoidCallback? onPressed; - final SvrntyButtonVariant variant; - final IconData? icon; - final bool isLoading; - final bool fullWidth; - - const SvrntyButton({ - Key? key, - required this.text, - this.onPressed, - this.variant = SvrntyButtonVariant.primary, - this.icon, - this.isLoading = false, - this.fullWidth = false, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - - Color backgroundColor; - Color textColor; - Color? borderColor; - - switch (variant) { - case SvrntyButtonVariant.primary: - backgroundColor = colorScheme.primary; - textColor = Colors.white; - borderColor = null; - break; - case SvrntyButtonVariant.secondary: - backgroundColor = colorScheme.secondary; - textColor = Colors.white; - borderColor = null; - break; - case SvrntyButtonVariant.ghost: - backgroundColor = Colors.transparent; - textColor = colorScheme.primary; - borderColor = colorScheme.primary; - break; - case SvrntyButtonVariant.danger: - backgroundColor = colorScheme.error; - textColor = Colors.white; - borderColor = null; - break; - } - - return FadeInUp( - duration: const Duration(milliseconds: 300), - child: SizedBox( - width: fullWidth ? double.infinity : null, - child: ElevatedButton( - onPressed: isLoading ? null : onPressed, - style: ElevatedButton.styleFrom( - backgroundColor: backgroundColor, - foregroundColor: textColor, - elevation: variant == SvrntyButtonVariant.ghost ? 0 : 2, - padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 14), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: borderColor != null - ? BorderSide(color: borderColor, width: 2) - : BorderSide.none, - ), - ), - child: isLoading - ? SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation(textColor), - ), - ) - : Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (icon != null) ...[ - Icon(icon, size: 20), - const SizedBox(width: 8), - ], - Text( - text, - style: const TextStyle( - fontWeight: FontWeight.w600, - fontSize: 16, - ), - ), - ], - ), - ), - ), - ); - } -} - -// ============================================================================ -// SVRNTY CARDS -// ============================================================================ - -class SvrntyCard extends StatelessWidget { - final Widget child; - final Color? accentColor; - final VoidCallback? onTap; - final bool showBorder; - final EdgeInsetsGeometry? padding; - - const SvrntyCard({ - Key? key, - required this.child, - this.accentColor, - this.onTap, - this.showBorder = true, - this.padding, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - final effectiveAccentColor = accentColor ?? colorScheme.primary; - - return FadeInUp( - duration: const Duration(milliseconds: 400), - child: Container( - decoration: showBorder - ? BoxDecoration( - borderRadius: BorderRadius.circular(16), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: [ - effectiveAccentColor.withOpacity(0.15), - effectiveAccentColor.withOpacity(0.05), - ], - ), - border: Border.all( - color: effectiveAccentColor.withOpacity(0.3), - width: 1, - ), - ) - : null, - child: Card( - elevation: 2, - color: showBorder ? Colors.transparent : null, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), - child: InkWell( - onTap: onTap, - borderRadius: BorderRadius.circular(16), - child: Container( - decoration: showBorder - ? BoxDecoration( - border: Border( - left: BorderSide( - color: effectiveAccentColor, - width: 4, - ), - ), - ) - : null, - child: Padding( - padding: padding ?? const EdgeInsets.all(20), - child: child, - ), - ), - ), - ), - ), - ); - } -} - -// ============================================================================ -// SVRNTY BADGES -// ============================================================================ - -enum SvrntyBadgeStatus { success, warning, error, info, neutral } - -class SvrntyBadge extends StatelessWidget { - final String text; - final SvrntyBadgeStatus status; - final IconData? icon; - - const SvrntyBadge({ - Key? key, - required this.text, - this.status = SvrntyBadgeStatus.neutral, - this.icon, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - - Color backgroundColor; - Color textColor; - - switch (status) { - case SvrntyBadgeStatus.success: - backgroundColor = Colors.green; - textColor = Colors.white; - break; - case SvrntyBadgeStatus.warning: - backgroundColor = Colors.orange; - textColor = Colors.white; - break; - case SvrntyBadgeStatus.error: - backgroundColor = colorScheme.error; - textColor = Colors.white; - break; - case SvrntyBadgeStatus.info: - backgroundColor = colorScheme.primary; - textColor = Colors.white; - break; - case SvrntyBadgeStatus.neutral: - backgroundColor = colorScheme.secondary; - textColor = Colors.white; - break; - } - - return Pulse( - duration: const Duration(milliseconds: 1000), - child: Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), - decoration: BoxDecoration( - color: backgroundColor, - borderRadius: BorderRadius.circular(20), - boxShadow: [ - BoxShadow( - color: backgroundColor.withOpacity(0.3), - blurRadius: 8, - offset: const Offset(0, 2), - ), - ], - ), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - if (icon != null) ...[ - Icon(icon, color: textColor, size: 16), - const SizedBox(width: 6), - ], - Text( - text, - style: TextStyle( - color: textColor, - fontSize: 12, - fontWeight: FontWeight.bold, - fontFamily: 'Montserrat', - ), - ), - ], - ), - ), - ); - } -} - -// ============================================================================ -// SVRNTY ICON BUTTONS -// ============================================================================ - -class SvrntyIconButton extends StatelessWidget { - final IconData icon; - final VoidCallback? onPressed; - final Color? backgroundColor; - final Color? iconColor; - final double size; - - const SvrntyIconButton({ - Key? key, - required this.icon, - this.onPressed, - this.backgroundColor, - this.iconColor, - this.size = 40, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - - return FadeIn( - child: Container( - width: size, - height: size, - decoration: BoxDecoration( - color: backgroundColor ?? colorScheme.primary.withOpacity(0.1), - shape: BoxShape.circle, - ), - child: IconButton( - icon: Icon(icon), - color: iconColor ?? colorScheme.primary, - iconSize: size * 0.5, - onPressed: onPressed, - ), - ), - ); - } -} - -// ============================================================================ -// SVRNTY SECTION HEADER -// ============================================================================ - -class SvrntySectionHeader extends StatelessWidget { - final String title; - final String? subtitle; - final Widget? action; - - const SvrntySectionHeader({ - Key? key, - required this.title, - this.subtitle, - this.action, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - - return FadeInLeft( - duration: const Duration(milliseconds: 400), - child: Row( - children: [ - Container( - width: 4, - height: 28, - decoration: BoxDecoration( - color: colorScheme.primary, - borderRadius: BorderRadius.circular(2), - ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: TextStyle( - fontSize: 22, - fontWeight: FontWeight.bold, - color: colorScheme.onSurface, - fontFamily: 'Montserrat', - ), - ), - if (subtitle != null) ...[ - const SizedBox(height: 4), - Text( - subtitle!, - style: TextStyle( - fontSize: 14, - color: colorScheme.onSurfaceVariant, - fontFamily: 'Montserrat', - ), - ), - ], - ], - ), - ), - if (action != null) action!, - ], - ), - ); - } -} - -// ============================================================================ -// SVRNTY INPUT FIELD -// ============================================================================ - -class SvrntyTextField extends StatelessWidget { - final String label; - final String? hint; - final TextEditingController? controller; - final IconData? prefixIcon; - final bool obscureText; - final TextInputType? keyboardType; - final String? Function(String?)? validator; - - const SvrntyTextField({ - Key? key, - required this.label, - this.hint, - this.controller, - this.prefixIcon, - this.obscureText = false, - this.keyboardType, - this.validator, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - final colorScheme = Theme.of(context).colorScheme; - - return FadeInUp( - duration: const Duration(milliseconds: 350), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - label, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w600, - color: colorScheme.onSurface, - fontFamily: 'Montserrat', - ), - ), - const SizedBox(height: 8), - TextFormField( - controller: controller, - obscureText: obscureText, - keyboardType: keyboardType, - validator: validator, - style: const TextStyle(fontFamily: 'Montserrat'), - decoration: InputDecoration( - hintText: hint, - prefixIcon: prefixIcon != null ? Icon(prefixIcon) : null, - filled: true, - fillColor: colorScheme.surfaceContainerHighest, - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: colorScheme.outline.withOpacity(0.3), - ), - ), - enabledBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: colorScheme.outline.withOpacity(0.2), - ), - ), - focusedBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: colorScheme.primary, - width: 2, - ), - ), - errorBorder: OutlineInputBorder( - borderRadius: BorderRadius.circular(12), - borderSide: BorderSide( - color: colorScheme.error, - width: 2, - ), - ), - contentPadding: const EdgeInsets.symmetric( - horizontal: 16, - vertical: 16, - ), - ), - ), - ], - ), - ); - } -} diff --git a/lib/console_landing_page.dart b/lib/console_landing_page.dart index 06f291a..e32b9de 100644 --- a/lib/console_landing_page.dart +++ b/lib/console_landing_page.dart @@ -2,9 +2,9 @@ import 'package:flutter/material.dart'; import 'package:iconsax/iconsax.dart'; import 'package:animate_do/animate_do.dart'; import 'package:getwidget/getwidget.dart'; +import 'package:url_launcher/url_launcher.dart'; import 'components/navigation_sidebar.dart'; import 'pages/architech_page.dart'; -import 'dart:html' as html; class ConsoleLandingPage extends StatefulWidget { const ConsoleLandingPage({Key? key}) : super(key: key); @@ -195,7 +195,7 @@ class _ConsoleLandingPageState extends State { colorScheme.primary, Icons.api, 'Active', - url: 'https://localhost:7108/swagger/', + url: 'http://localhost:7108/swagger/', ), _buildStatusCard( 'Frontend', @@ -306,7 +306,7 @@ class _ConsoleLandingPageState extends State { return GridView.builder( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: crossAxisCount, - mainAxisExtent: 140, + mainAxisExtent: 155, crossAxisSpacing: spacing, mainAxisSpacing: spacing, ), @@ -353,9 +353,46 @@ class _ConsoleLandingPageState extends State { borderRadius: BorderRadius.circular(16), ), child: InkWell( - onTap: () { + onTap: () async { if (url != null) { - html.window.open(url, '_blank'); + try { + final uri = Uri.parse(url); + if (await canLaunchUrl(uri)) { + await launchUrl(uri, mode: LaunchMode.externalApplication); + if (context.mounted) { + GFToast.showToast( + 'Opening $title...', + context, + toastPosition: GFToastPosition.BOTTOM, + textStyle: const TextStyle(fontSize: 14, color: Colors.white), + backgroundColor: color.withOpacity(0.9), + toastDuration: 2, + ); + } + } else { + if (context.mounted) { + GFToast.showToast( + 'Cannot open $url', + context, + toastPosition: GFToastPosition.BOTTOM, + textStyle: const TextStyle(fontSize: 14, color: Colors.white), + backgroundColor: colorScheme.error.withOpacity(0.9), + toastDuration: 3, + ); + } + } + } catch (e) { + if (context.mounted) { + GFToast.showToast( + 'Error: ${e.toString()}', + context, + toastPosition: GFToastPosition.BOTTOM, + textStyle: const TextStyle(fontSize: 14, color: Colors.white), + backgroundColor: colorScheme.error.withOpacity(0.9), + toastDuration: 3, + ); + } + } } else { GFToast.showToast( '$title tapped', diff --git a/lib/main.dart b/lib/main.dart index b379191..7d2faa7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -22,89 +22,3 @@ class MyApp extends StatelessWidget { ); } } - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } - - @override - Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], - ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. - ); - } -} diff --git a/macos/Podfile b/macos/Podfile new file mode 100644 index 0000000..ff5ddb3 --- /dev/null +++ b/macos/Podfile @@ -0,0 +1,42 @@ +platform :osx, '10.15' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/pubspec.yaml b/pubspec.yaml index 674eaa4..1b8f768 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ -name: my_app -description: "A new Flutter project." +name: console +description: "Svrnty Console - Sovereign AI Solutions control panel." # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev @@ -41,6 +41,7 @@ dependencies: iconsax: ^0.0.8 # Modern clean icons flutter_animate: ^4.3.0 # Advanced animations getwidget: ^7.0.0 # Modern UI component library (compatible with Flutter 3.35.0+) + url_launcher: ^6.3.1 # Cross-platform URL launching dev_dependencies: flutter_test: diff --git a/test/widget_test.dart b/test/widget_test.dart index f23f096..0486dd3 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -1,30 +1,19 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. +// Svrnty Console Widget Tests -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:my_app/main.dart'; +import 'package:console/main.dart'; void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { + testWidgets('App launches and displays Console UI', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(const MyApp()); - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); + // Verify that the app title is set correctly. + expect(find.text('Svrnty'), findsOneWidget); + expect(find.text('Console'), findsOneWidget); - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); + // Verify dashboard is displayed by default. + expect(find.text('Dashboard'), findsWidgets); }); }