Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 55 additions & 2 deletions docs/cli.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ description: "Build, deploy, and manage Server-Driven UI projects with the Stac
| `status` | Show authentication status | No |
| `init` | Initialize Stac in project | Yes |
| `build` | Convert Dart widgets to JSON | No |
| `dev` | Run local widget development | No |
| `deploy` | Build and deploy to Stac Cloud | Yes |
| `project list` | List all cloud projects | Yes |
| `project create` | Create new cloud project | Yes |
Expand Down Expand Up @@ -128,7 +129,59 @@ stac build --verbose
| `--validate` | Validate generated JSON | `true` |
| `-v, --verbose` | Show detailed build output | `false` |

The build command converts Stac widget definitions from the `stac/` folder into JSON format in the `build/` folder.
The build command converts Stac widget definitions from the `stac/` folder into JSON format in `stac/.build/`.

### Develop Locally

Use `stac dev` to test `Stac(routeName: ...)` screens in a running app without deploying to Stac Cloud. It builds your local Stac DSL, serves the generated `stac/.build` JSON through cloud-compatible `/screens` and `/themes` endpoints, and rebuilds when files are saved.

```bash
stac dev
```

Serve a specific project:

```bash
stac dev --project /path/to/project
```

Use a custom host and port:

```bash
stac dev --host 0.0.0.0 --port 45700
```

Skip the initial build and serve existing build files:

```bash
stac dev --skip-build
```

Then point your debug app at the local server:

```dart
import 'package:flutter/foundation.dart';

await Stac.initialize(
options: defaultStacOptions.copyWith(
apiBaseUrl: kDebugMode
? 'http://127.0.0.1:45700'
: defaultStacOptions.apiBaseUrl,
),
);
```

`stac dev` prints ready-to-copy URLs for local/iOS Simulator/web, Android Emulator, and physical devices. For a physical device, start with `stac dev --host 0.0.0.0` and use the printed LAN URL as `StacOptions.apiBaseUrl`.

#### Dev Options

| Option | Description | Default |
| --------------- | ---------------------------------------- | ----------------- |
| `-p, --project` | Project directory path | Current directory |
| `--host` | Host address for the local server | `127.0.0.1` |
| `--port` | Port for the local server | `45700` |
| `--skip-build` | Serve existing files without first build | `false` |
| `--watch` | Rebuild local JSON when Stac files save | `true` |

### Deploy to Stac Cloud

Expand Down Expand Up @@ -237,4 +290,4 @@ stac deploy
| --------------- | ------------------------------ |
| `-v, --verbose` | Show additional command output |
| `--version` | Print tool version |
| `--help` | Print usage information |
| `--help` | Print usage information |
26 changes: 25 additions & 1 deletion docs/quickstart.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,30 @@ StacWidget helloWorld() {

Stac follows Flutter's conventions for building widgets. For example, to use Flutter's `Scaffold`, use the `StacScaffold` widget.

## Test Locally

During development, you can test Stac screens in your running app without deploying to Stac Cloud:

```bash
stac dev
```

Then point debug builds at the local server:

```dart
import 'package:flutter/foundation.dart';

await Stac.initialize(
options: defaultStacOptions.copyWith(
apiBaseUrl: kDebugMode
? 'http://127.0.0.1:45700'
: defaultStacOptions.apiBaseUrl,
),
);
```

`stac dev` rebuilds local JSON when files in `stac/` are saved and prints ready-to-copy URLs for local/iOS Simulator/web, Android Emulator, and physical devices. For a physical device, start with `stac dev --host 0.0.0.0` and use the printed LAN URL.

## Deploy Stac widgets

Now that we have our widget built, we can deploy it to Stac Cloud.
Expand Down Expand Up @@ -239,4 +263,4 @@ You're all set. Next, explore widgets and actions, or jump into the CLI guide:
- [Actions](/actions/navigate)
- [CLI](/cli)

Need help? Join the community on [Discord](https://discord.com/invite/vTGsVRK86V) or open an issue on [GitHub](https://github.com/StacDev/stac/issues).
Need help? Join the community on [Discord](https://discord.com/invite/vTGsVRK86V) or open an issue on [GitHub](https://github.com/StacDev/stac/issues).
2 changes: 0 additions & 2 deletions examples/movie_app/ios/Flutter/AppFrameworkInfo.plist
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>13.0</string>
</dict>
</plist>
7 changes: 5 additions & 2 deletions examples/movie_app/ios/Runner/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ import Flutter
import UIKit

@main
@objc class AppDelegate: FlutterAppDelegate {
@objc class AppDelegate: FlutterAppDelegate, FlutterImplicitEngineDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}

func didInitializeImplicitFlutterEngine(_ engineBridge: FlutterImplicitEngineBridge) {
GeneratedPluginRegistrant.register(with: engineBridge.pluginRegistry)
}
}
29 changes: 25 additions & 4 deletions examples/movie_app/ios/Runner/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
Expand All @@ -24,6 +26,29 @@
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
Expand All @@ -41,9 +66,5 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
</dict>
</plist>
2 changes: 1 addition & 1 deletion examples/movie_app/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ void main() async {
);

await Stac.initialize(
options: defaultStacOptions,
options: defaultStacOptions.copyWith(apiBaseUrl: 'http://127.0.0.1:45700'),
dio: dio,
parsers: [MovieCarouselParser()],
);
Expand Down
2 changes: 1 addition & 1 deletion examples/movie_app/stac/app_theme.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ StacTheme get darkTheme => _buildTheme(
brightness: StacBrightness.dark,
colorScheme: StacColorScheme(
brightness: StacBrightness.dark,
primary: '#95E183',
primary: '#212121',
onPrimary: '#050608',
Comment on lines +8 to 9

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
python3 - <<'PY'
def lum(hex_):
    h=hex_.lstrip('#'); r,g,b=(int(h[i:i+2],16)/255 for i in (0,2,4))
    f=lambda c:(c/12.92) if c<=0.03928 else ((c+0.055)/1.055)**2.4
    R,G,B=f(r),f(g),f(b)
    return 0.2126*R+0.7152*G+0.0722*B
def ratio(a,b):
    la,lb=lum(a),lum(b); hi,lo=max(la,lb),min(la,lb)
    return (hi+0.05)/(lo+0.05)
print("primary/onPrimary:", round(ratio('`#212121`','`#050608`'),2))
PY

Repository: StacDev/stac

Length of output: 80


Fix onPrimary contrast against primary
In examples/movie_app/stac/app_theme.dart (primary #212121, onPrimary #050608), the WCAG contrast ratio is ~1.26:1, far below AA (4.5:1 normal / 3:1 large). Update onPrimary to a significantly lighter content color (or adjust primary) so text/icons on primary surfaces remain readable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/movie_app/stac/app_theme.dart` around lines 8 - 9, The current color
pair primary: '`#212121`' and onPrimary: '`#050608`' fails WCAG contrast; update the
onPrimary value (or adjust primary) so the contrast ratio is >= 4.5:1 for normal
text (or >= 3:1 for large text). Locate the primary and onPrimary declarations
in app_theme.dart and replace onPrimary with a much lighter color (e.g.,
'`#FFFFFF`' or a light gray like '`#F5F5F5`') or compute a contrast-safe foreground
dynamically; ensure the chosen onPrimary yields at least 4.5:1 contrast against
'`#212121`' before committing.

secondary: '#95E183',
onSecondary: '#FFFFFF',
Expand Down
10 changes: 9 additions & 1 deletion packages/stac/lib/src/services/stac_cloud.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,15 @@ class StacCloud {
),
);

static const String _baseUrl = 'https://api.stac.dev';
static const String _defaultBaseUrl = 'https://api.stac.dev';

static String get _baseUrl {
final configuredBaseUrl = StacService.options?.apiBaseUrl.trim();
if (configuredBaseUrl == null || configuredBaseUrl.isEmpty) {
return _defaultBaseUrl;
}
return configuredBaseUrl.replaceFirst(RegExp(r'/+$'), '');
}

/// Gets the fetch URL for a given artifact type.
static String _getFetchUrl(StacArtifactType artifactType) {
Expand Down
7 changes: 6 additions & 1 deletion packages/stac_cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ stac --version
stac login
stac init
stac build
stac dev
stac deploy
```

Use `stac dev` during local development to build and serve Stac screens from
`stac/.build` without deploying to Stac Cloud. Point debug builds at the local
server with `defaultStacOptions.copyWith(apiBaseUrl: 'http://127.0.0.1:45700')`.
The command also prints local/iOS, Android Emulator, and physical-device URLs.

## Environment

The CLI reads credentials from:
Expand All @@ -41,4 +47,3 @@ Required keys:
- `STAC_FIREBASE_API_KEY`

Set environment in code via `currentEnvironment` in `lib/src/config/env.dart`.

2 changes: 2 additions & 0 deletions packages/stac_cli/bin/stac_cli.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import 'package:stac_cli/src/commands/auth/logout_command.dart';
import 'package:stac_cli/src/commands/auth/status_command.dart';
import 'package:stac_cli/src/commands/build_command.dart';
import 'package:stac_cli/src/commands/deploy_command.dart';
import 'package:stac_cli/src/commands/dev_command.dart';
import 'package:stac_cli/src/commands/init_command.dart';
import 'package:stac_cli/src/commands/project_command.dart';
import 'package:stac_cli/src/commands/upgrade_command.dart';
Expand Down Expand Up @@ -66,6 +67,7 @@ void main(List<String> arguments) async {
..addCommand(InitCommand())
..addCommand(ProjectCommand())
..addCommand(BuildCommand())
..addCommand(DevCommand())
..addCommand(DeployCommand())
..addCommand(UpgradeCommand());

Expand Down
2 changes: 1 addition & 1 deletion packages/stac_cli/lib/src/commands/deploy_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'base_command.dart';
/// Command for deploying JSON files to the cloud
class DeployCommand extends BaseCommand {
final BuildService _buildService = BuildService();
final DeployService _deployService = DeployService();
late final DeployService _deployService = DeployService();

@override
String get name => 'deploy';
Expand Down
76 changes: 76 additions & 0 deletions packages/stac_cli/lib/src/commands/dev_command.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'package:stac_cli/src/services/dev_service.dart';
import 'package:stac_cli/src/utils/console_logger.dart';

import 'base_command.dart';

class DevCommand extends BaseCommand {
DevCommand({DevService? devService})
: _devService = devService ?? DevService() {
argParser.addOption(
'project',
abbr: 'p',
help: 'Project directory (defaults to current directory)',
);
argParser.addOption(
'host',
help: 'Host address for the local development server',
defaultsTo: '127.0.0.1',
);
argParser.addOption(
'port',
help: 'Port for the local development server',
defaultsTo: '45700',
);
argParser.addFlag(
'skip-build',
help: 'Use existing build files without running the initial build',
negatable: false,
);
argParser.addFlag(
'watch',
help: 'Watch Stac source files and rebuild on save',
defaultsTo: true,
);
}

final DevService _devService;

@override
String get name => 'dev';

@override
String get description =>
'Run a local Stac development server for screens and themes';

@override
bool get requiresProject => true;

@override
Future<int> execute() async {
final projectPath = argResults?['project'] as String?;
final host = argResults?['host'] as String? ?? '127.0.0.1';
final portValue = argResults?['port'] as String? ?? '45700';
final port = int.tryParse(portValue);
final skipBuild = argResults?['skip-build'] as bool? ?? false;
final watch = argResults?['watch'] as bool? ?? true;

if (port == null || port < 0 || port > 65535) {
ConsoleLogger.error('Invalid port: $portValue');
return 1;
}

try {
await _devService.serve(
projectPath: projectPath,
host: host,
port: port,
skipBuild: skipBuild,
watch: watch,
);
return 0;
} catch (e) {
ConsoleLogger.error('Dev failed: $e');
return 1;
}
}
}
2 changes: 1 addition & 1 deletion packages/stac_cli/lib/src/commands/init_command.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'base_command.dart';

/// Command for initializing a Stac project from cloud projects
class InitCommand extends BaseCommand {
final ProjectService _projectService = ProjectService();
late final ProjectService _projectService = ProjectService();

@override
String get name => 'init';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import '../../utils/console_logger.dart';

/// Command for creating a new project on the cloud
class CreateCommand extends BaseCommand {
final ProjectService _projectService = ProjectService();
late final ProjectService _projectService = ProjectService();

@override
String get name => 'create';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import '../base_command.dart';

/// Command for listing all cloud projects
class ListCommand extends BaseCommand {
final ProjectService _projectService = ProjectService();
late final ProjectService _projectService = ProjectService();

@override
String get name => 'list';
Expand Down
Loading
Loading