Skip to content

Conversation

@Potatomonsta
Copy link
Contributor

@Potatomonsta Potatomonsta commented Dec 11, 2025

Description

This PR adds comprehensive theme support to the Stac framework, enabling themes to be loaded from Stac Cloud similar to how screens are currently loaded. The implementation includes async theme loading, intelligent caching, and proper context handling.

Key Changes

New Features:

  • Added StacArtifactType enum to support both screens and themes as fetchable artifacts
  • Added StacCloudTheme annotation for marking theme builder methods
  • Added StacAppTheme helper class with methods to load themes from:
    • Stac Cloud (fromCloud)
    • Network requests (fromNetwork)
    • Raw JSON (fromJson)
  • Extended StacApp to support FutureOr<StacTheme?> for both theme and darkTheme parameters, enabling async theme loading
  • Added loading state handling with _ThemeFutureLoading widget when themes are being fetched

Refactoring:

  • Refactored StacCacheService to be artifact-type agnostic:
    • Renamed methods from screen-specific to artifact-specific (getCachedArtifact, saveArtifact, etc.)
    • Added artifact type parameter to all cache operations
    • Supports both 'screen' and 'theme' artifact types
  • Refactored StacCloud to support fetching both screens and themes:
    • Unified fetching logic in _fetchArtifact method
    • Added fetchTheme method as public API
    • Updated background fetch tracking to support multiple artifact types
    • Added theme-specific cache management methods (clearThemeCache, clearAllThemeCache)

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Bug fix (non-breaking change which fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Code refactor
  • Build configuration change
  • Documentation
  • Chore

Summary by CodeRabbit

  • New Features

    • Multi-source theming (DSL/cloud/network/JSON) via a StacAppTheme wrapper
    • Theme annotation for runtime registration
    • Artifact type enum to classify cached items
    • Cloud APIs to fetch/clear themes
  • Refactor

    • App now resolves themes asynchronously and shows a loading indicator
    • Cache model renamed to artifact-aware StacCache with per-type cache APIs
  • Documentation

    • Expanded theming guide and added theming page to docs navigation

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 11, 2025

📝 Walkthrough

Walkthrough

This PR introduces multi-source theming via StacAppTheme (DSL/Cloud/Network/JSON), migrates StacApp to a StatefulWidget with async theme resolution and loading state, generalizes caching to artifact-type aware APIs and model (StacCache/StacArtifactType), and adds a StacThemeRef annotation.

Changes

Cohort / File(s) Summary
Core Theming Infrastructure
packages/stac/lib/src/framework/stac_app_theme.dart, packages/stac/lib/src/framework/stac_app.dart, packages/stac_core/lib/annotations/stac_theme_ref.dart
Add StacAppTheme (dsl/cloud/network/json) with async resolve(). Convert StacApp from StatelessWidget → StatefulWidget; resolve themes asynchronously and show loading state. Add @StacThemeRef annotation.
Caching & Cloud
packages/stac/lib/src/services/stac_cache_service.dart, packages/stac/lib/src/services/stac_cloud.dart
Rename cache APIs to artifact-aware variants, add StacArtifactType, per-type cache prefixes, artifact-aware fetch/clear/save/remove, and theme fetch/clear endpoints. Background fetch tracking scoped by artifact type.
Models & Serialization
packages/stac/lib/src/models/stac_cache.dart, packages/stac/lib/src/models/stac_cache.g.dart, packages/stac/lib/src/models/stac_artifact_type.dart, packages/stac/lib/src/models/models.dart
Rename StacScreenCacheStacCache (models, serializers, generated g.dart). Add StacArtifactType enum. Update models barrel to export stac_cache.dart.
Barrels / Public API
packages/stac/lib/src/framework/framework.dart, packages/stac_core/lib/annotations/annotations.dart
Export stac_app_theme.dart from framework barrel and stac_theme_ref.dart from annotations barrel (extend public exports).
Examples
examples/movie_app/lib/main.dart, examples/movie_app/lib/themes/app_theme.dart, examples/movie_app/stac/app_theme.dart, examples/stac_gallery/lib/main.dart
Move movie_app theme into stac/ with @StacThemeRef("movie_app_dark"); remove old local theme file; update apps to pass StacAppTheme instances (JSON/cloud references) instead of raw theme objects.
Docs / Navigation
docs/concepts/theming.mdx, docs/docs.json
Replace single-source theming doc with a multi-source guide and examples; add concepts/theming to docs navigation.

Sequence Diagram(s)

sequenceDiagram
    participant App as StacApp (Widget)
    participant State as _StacAppState (State)
    participant Resolver as _resolveThemes()
    participant AppTheme as StacAppTheme
    participant Source as Theme Source (Cloud/Network/DSL/JSON)
    participant UI as MaterialApp / UI

    App->>State: initState()
    activate State
    State->>Resolver: _resolveThemes()
    activate Resolver
    Resolver->>AppTheme: theme.resolve()
    activate AppTheme
    alt DSL
        AppTheme->>Source: return DSL theme
    else Cloud
        AppTheme->>Source: fromCloud(themeName)
    else Network
        AppTheme->>Source: fromNetwork(context, request)
    else JSON
        AppTheme->>Source: fromJson(payload)
    end
    Source-->>AppTheme: StacTheme? (async)
    deactivate AppTheme
    Resolver-->>State: _ResolvedStacThemes
    deactivate Resolver
    State->>UI: setState() → build()
    alt Resolved
        State->>UI: MaterialApp / MaterialApp.router with parsed themes
    else Loading / Error
        State->>UI: CircularProgressIndicator or fallback
    end
    deactivate State
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • #386 — Related refactor of caching and cloud fetch subsystems that introduce artifact/cache model changes.
  • #381 — Example migration for movie_app theming and theme file moves that overlap with this PR.
  • #379 — Theming/DSL primitives in stac_core that relate to StacThemeRef and theme discovery.

Suggested reviewers

  • divyanshub024

Poem

🐰 A rabbit hops through themes so bright,

From cloud, JSON, DSL — a multi-source light,
It waits while resolves hum soft and slow,
Caches learn their types as they come and go,
Hooray — new themes to make each app glow! 🎨

Pre-merge checks

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main feature: adding theme support with cloud loading capability to the Stac framework.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4293b1e and dd9561a.

📒 Files selected for processing (1)
  • packages/stac/lib/src/services/stac_cloud.dart
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-19T20:20:55.561Z
Learnt from: akhil-ge0rge
Repo: StacDev/stac PR: 399
File: packages/stac/lib/src/parsers/theme/stac_tool_tip_theme_data_parser.dart:9-26
Timestamp: 2025-12-19T20:20:55.561Z
Learning: In Flutter, TooltipThemeData.height is deprecated since v3.30.0-0.1.pre. Replace uses with TooltipThemeData.constraints: BoxConstraints(minHeight: value) to achieve the same minimum height. Apply this pattern to review any Dart/Flutter code that uses TooltipThemeData.height, not just this file.

Applied to files:

  • packages/stac/lib/src/services/stac_cloud.dart
🔇 Additional comments (7)
packages/stac/lib/src/services/stac_cloud.dart (7)

25-43: LGTM!

The helper methods _getFetchUrl and _getQueryParamName provide clean abstraction for artifact-type-specific URLs and query parameters. The exhaustive switch statements ensure compile-time safety when artifact types are added.


60-139: LGTM!

The unified _fetchArtifact method successfully generalizes the fetch logic to support multiple artifact types while maintaining all caching strategies. The early returns for network-only and cache-only strategies are efficient, and the strategy dispatch is clear.


150-161: LGTM!

The fetchScreen method cleanly delegates to the unified _fetchArtifact logic while maintaining backward compatibility with the existing public API.


164-255: LGTM!

The strategy handler methods (_handleArtifactNetworkFirst, _handleArtifactCacheFirst, _handleArtifactOptimistic) are correctly refactored to be artifact-agnostic. The logic preserves all previous behaviors while adding artifact type context to log messages for better observability.


257-319: LGTM!

The network operation methods (_makeArtifactRequest, _fetchArtifactFromNetwork, _buildArtifactCacheResponse) correctly implement artifact-aware fetch and caching logic. Request construction properly uses the artifact-specific helpers, and cache operations include the artifact type parameter.


370-390: LGTM!

The new fetchTheme method provides excellent API symmetry with fetchScreen, maintaining consistent design patterns and documentation. The delegation to _fetchArtifact with StacArtifactType.theme is correct.


392-410: LGTM!

The cache clearing methods are correctly updated to use artifact-aware cache operations, and new theme-specific methods (clearThemeCache, clearAllThemeCache) mirror the screen methods for excellent API consistency.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (9)
packages/stac/lib/src/framework/stac_app.dart (2)

270-280: Consider resolving theme futures in parallel.

Both theme futures are awaited sequentially. Using Future.wait would allow parallel resolution.

     return Future<_ResolvedStacThemes>(() async {
-      final resolvedTheme =
-          await (themeFuture ?? Future<StacTheme?>.value(themeValue));
-      final resolvedDarkTheme =
-          await (darkThemeFuture ?? Future<StacTheme?>.value(darkThemeValue));
+      final results = await Future.wait([
+        themeFuture ?? Future<StacTheme?>.value(themeValue),
+        darkThemeFuture ?? Future<StacTheme?>.value(darkThemeValue),
+      ]);
 
       return _ResolvedStacThemes(
-        theme: resolvedTheme,
-        darkTheme: resolvedDarkTheme,
+        theme: results[0],
+        darkTheme: results[1],
       );
     });

317-324: Loading widget has no theme context.

This widget renders before MaterialApp is built, so there's no inherited theme. The Material widget will use fallback colors which may cause a visual flash when the app loads. Consider specifying explicit colors.

   @override
   Widget build(BuildContext context) {
-    return const Material(child: Center(child: CircularProgressIndicator()));
+    return const ColoredBox(
+      color: Colors.white, // Or Colors.transparent for less jarring transition
+      child: Center(child: CircularProgressIndicator()),
+    );
   }
packages/stac/lib/src/framework/stac_app_theme.dart (1)

16-33: Silent null returns may hinder debugging.

Multiple failure paths return null without any indication of what went wrong. Consider adding debug logging to help diagnose theme loading issues in development.

   static Future<StacTheme?> fromCloud({required String themeName}) async {
     final response = await StacCloud.fetchTheme(themeName: themeName);
     if (response == null) {
+      assert(() { debugPrint('StacAppTheme.fromCloud: fetchTheme returned null for "$themeName"'); return true; }());
       return null;
     }

     final rawData = response.data;
     if (rawData is! Map<String, dynamic>) {
+      assert(() { debugPrint('StacAppTheme.fromCloud: response.data is not Map<String, dynamic>'); return true; }());
       return null;
     }
packages/stac/lib/src/services/stac_cache_service.dart (2)

19-29: Use StacArtifactType enum instead of string for type safety.

The artifactType parameter accepts any string, but only 'screen' and 'theme' are valid. Using the StacArtifactType enum would provide compile-time safety and prevent runtime ArgumentError.

-  static String _getCachePrefix(String artifactType) {
-    switch (artifactType) {
-      case 'screen':
+  static String _getCachePrefix(StacArtifactType artifactType) {
+    switch (artifactType) {
+      case StacArtifactType.screen:
         return 'stac_screen_cache_';
-      case 'theme':
+      case StacArtifactType.theme:
         return 'stac_theme_cache_';
-      default:
-        throw ArgumentError('Unknown artifact type: $artifactType');
     }
   }

This would require updating all method signatures to accept StacArtifactType instead of String.


48-48: StacScreenCache class used for theme artifacts.

The StacScreenCache class is now used to cache both screens and themes, which is a naming inconsistency. Consider renaming to StacArtifactCache for clarity, or document that the class is intentionally reused.

Also applies to: 68-73

packages/stac/lib/src/models/stac_artifact_type.dart (1)

1-8: Inconsistent artifact type handling between StacCloud and StacCacheService.

The StacArtifactType enum is actively used throughout StacCloud (via switch statements and type-safe parameters), but when StacCloud calls StacCacheService methods (e.g., clearScreenCache, clearThemeCache), it passes hardcoded strings ('screen', 'theme') instead of enum values. Meanwhile, StacCacheService._getCachePrefix() switches on these string literals. Consider having StacCacheService accept StacArtifactType instead of String to maintain type consistency and avoid string literal brittleness at the boundary.

packages/stac/lib/src/services/stac_cloud.dart (3)

69-150: Tighten strategy handling in _fetchArtifact to avoid confusing dead branch

The overall flow (early handling of networkOnly / cacheOnly, then switching on the remaining strategies) is good, but the final cacheOnly/networkOnly cases in the switch are now unreachable and contradict the comment:

case StacCacheStrategy.cacheOnly:
case StacCacheStrategy.networkOnly:
  // Already handled above
  return _fetchArtifactFromNetwork(
    artifactType: artifactType,
    artifactName: artifactName,
    saveToCache: false,
  );

Because both strategies are already fully handled by the if branches above (return/throw), this code will never run, but if someone refactors out the early ifs later it would silently change cacheOnly to “network-only, no cache”.

Consider either:

  • Moving networkOnly/cacheOnly handling entirely into the switch and dropping the early ifs, or
  • Keeping the early ifs and turning these two cases into an explicit unreachable/StateError so any future misuse is caught quickly.

For example:

       case StacCacheStrategy.optimistic:
         return _handleArtifactOptimistic(
           artifactType: artifactType,
           artifactName: artifactName,
           cachedArtifact: cachedArtifact,
           isCacheValid: isCacheValid,
           config: cacheConfig,
         );
 
       case StacCacheStrategy.cacheOnly:
       case StacCacheStrategy.networkOnly:
-        // Already handled above
-        return _fetchArtifactFromNetwork(
-          artifactType: artifactType,
-          artifactName: artifactName,
-          saveToCache: false,
-        );
+        // These should have been handled by the early returns above.
+        throw StateError(
+          'Unexpected strategy ${cacheConfig.strategy} in _fetchArtifact; '
+          'cacheOnly/networkOnly should be handled before the switch.',
+        );

Functionally nothing is broken today, but this makes the intent clearer and safer for future changes.


174-266: Strategy helpers read correctly and match the intended semantics

_handleArtifactNetworkFirst, _handleArtifactCacheFirst, and _handleArtifactOptimistic correctly implement the advertised behaviors (network-first with cache fallback, cache-first with optional background refresh and stale-while-revalidate, and optimistic with background update). Logging with artifactType.name is helpful for debugging.

The only (very minor) nit is that StacScreenCache is now effectively an artifact cache model for both screens and themes; if you ever generalize more artifact types, you might consider renaming it to something like StacArtifactCache for clarity, but that would be a larger cross-file change.


381-421: Theme fetch/clear helpers mirror screen APIs; consider removing hard-coded type strings

The new fetchTheme, clearThemeCache, and clearAllThemeCache helpers align well with the existing screen APIs and the artifact-type abstraction.

For the cache-clear methods, you’re still using string literals 'screen' / 'theme':

static Future<bool> clearScreenCache(String routeName) {
  return StacCacheService.removeArtifact(routeName, 'screen');
}

static Future<bool> clearAllCache() {
  return StacCacheService.clearAllArtifacts('screen');
}

static Future<bool> clearThemeCache(String themeName) {
  return StacCacheService.removeArtifact(themeName, 'theme');
}

static Future<bool> clearAllThemeCache() {
  return StacCacheService.clearAllArtifacts('theme');
}

Since you already have _getArtifactTypeString, you can avoid duplicating these strings and keep everything enum-driven:

 static Future<bool> clearScreenCache(String routeName) {
-  return StacCacheService.removeArtifact(routeName, 'screen');
+  return StacCacheService.removeArtifact(
+    routeName,
+    _getArtifactTypeString(StacArtifactType.screen),
+  );
 }
 
 static Future<bool> clearAllCache() {
-  return StacCacheService.clearAllArtifacts('screen');
+  return StacCacheService.clearAllArtifacts(
+    _getArtifactTypeString(StacArtifactType.screen),
+  );
 }
 
 static Future<bool> clearThemeCache(String themeName) {
-  return StacCacheService.removeArtifact(themeName, 'theme');
+  return StacCacheService.removeArtifact(
+    themeName,
+    _getArtifactTypeString(StacArtifactType.theme),
+  );
 }
 
 static Future<bool> clearAllThemeCache() {
-  return StacCacheService.clearAllArtifacts('theme');
+  return StacCacheService.clearAllArtifacts(
+    _getArtifactTypeString(StacArtifactType.theme),
+  );
 }

This keeps the cache-layer contract tied to the enum and avoids subtle drift if the underlying type strings ever change.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d4ebbca and 43ffe4f.

📒 Files selected for processing (8)
  • packages/stac/lib/src/framework/framework.dart (1 hunks)
  • packages/stac/lib/src/framework/stac_app.dart (7 hunks)
  • packages/stac/lib/src/framework/stac_app_theme.dart (1 hunks)
  • packages/stac/lib/src/models/stac_artifact_type.dart (1 hunks)
  • packages/stac/lib/src/services/stac_cache_service.dart (4 hunks)
  • packages/stac/lib/src/services/stac_cloud.dart (7 hunks)
  • packages/stac_core/lib/annotations/annotations.dart (1 hunks)
  • packages/stac_core/lib/annotations/stac_cloud_theme.dart (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: analyze
🔇 Additional comments (9)
packages/stac_core/lib/annotations/annotations.dart (1)

3-4: New stac_cloud_theme.dart export looks consistent

Exporting the new annotation alongside stac_screen.dart keeps the annotations barrel file up to date and is aligned with how other annotations are exposed.

packages/stac_core/lib/annotations/stac_cloud_theme.dart (1)

13-18: Annotation shape is simple and appropriate for intended use

Const constructor with a required themeName and a single final field is a clean, idiomatic way to model this annotation and should work well for discovery/registration of theme builders.

packages/stac/lib/src/framework/stac_app.dart (1)

113-114: Good backward-compatible API change.

The change from StacTheme? to FutureOr<StacTheme?>? maintains backward compatibility - existing synchronous theme assignments continue to work while enabling async loading.

packages/stac/lib/src/framework/framework.dart (1)

5-5: LGTM!

Export added to expose the new StacAppTheme utility class through the framework's public API surface.

packages/stac/lib/src/framework/stac_app_theme.dart (1)

39-49: LGTM!

The fromNetwork method correctly delegates to StacNetworkService.request and handles null responses gracefully. The BuildContext usage is appropriate as it's passed directly to the service.

packages/stac/lib/src/services/stac_cache_service.dart (2)

130-139: LGTM!

The cache validity logic is clear and handles edge cases properly - null artifact returns false, null maxAge means no expiration. The age comparison is straightforward.


97-110: LGTM!

Good use of Future.wait for parallel key removal, and prefix-based filtering correctly isolates artifacts by type.

packages/stac/lib/src/services/stac_cloud.dart (2)

26-52: Helper extraction for artifact URLs and params looks solid

Centralizing _baseUrl, _getFetchUrl, _getQueryParamName, and _getArtifactTypeString keeps screen/theme handling consistent and makes it easy to extend to new artifact types. No changes requested here.


268-331: Network + cache integration for artifacts looks consistent

_makeArtifactRequest, _fetchArtifactFromNetwork, and _buildArtifactCacheResponse nicely generalize the old screen-only path:

  • Request URL and query param are derived from the artifact type.
  • Cache read/write uses _getArtifactTypeString(artifactType) consistently, so screens and themes share the same caching mechanism with type partitioning.
  • Cached responses are shaped identically to network ones (name, stacJson, version), which keeps downstream consumers simple.

No issues from a correctness standpoint; just ensure the Cloud endpoints for both screens and themes always return that { name, stacJson, version } shape so caching works uniformly.

Comment on lines 6 to 12
/// Example usage:
/// ```dart
/// @StacThemeAnnotation(themeName: 'darkTheme')
/// ThemeData buildDarkTheme() {
/// return ThemeData.dark();
/// }
/// ```
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 11, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Doc example uses the wrong annotation name

The example shows @StacThemeAnnotation but this file defines StacCloudTheme. That mismatch can confuse users and docs/tools.

Consider updating the example to use the actual annotation:

-/// @StacThemeAnnotation(themeName: 'darkTheme')
+/// @StacCloudTheme(themeName: 'darkTheme')
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/// Example usage:
/// ```dart
/// @StacThemeAnnotation(themeName: 'darkTheme')
/// ThemeData buildDarkTheme() {
/// return ThemeData.dark();
/// }
/// ```
/// Example usage:
///
🤖 Prompt for AI Agents
In packages/stac_core/lib/annotations/stac_cloud_theme.dart around lines 6 to
12, the documentation example uses the wrong annotation name
(`@StacThemeAnnotation`) which doesn't match the defined annotation
`StacCloudTheme`; update the example to use the actual annotation (e.g.,
`@StacCloudTheme(themeName: 'darkTheme')`) and ensure the annotated function
signature in the example matches the library's expected usage so docs and tools
are consistent.

Copy link
Member

Choose a reason for hiding this comment

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

Can we name it StacThemeConfig?

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for any usages of the StacCloudTheme annotation in the codebase
echo "=== Searching for StacCloudTheme usages ==="
rg -n "StacCloudTheme" --type dart -C 2

echo -e "\n=== Searching for @StacCloudTheme annotation usage ==="
rg -n "@StacCloudTheme" --type dart -C 2

echo -e "\n=== Searching for stac_cloud_theme import references ==="
rg -n "stac_cloud_theme" --type dart -C 2

Length of output: 985


@divyanshub024 Sure, renaming to StacThemeConfig makes sense! Since this annotation isn't used anywhere in the codebase yet (it's new in this PR), the rename would be straightforward. Here's what would need to be updated:

  1. Class name in packages/stac_core/lib/annotations/stac_cloud_theme.dart:

    • class StacCloudThemeclass StacThemeConfig
    • Constructor and doc references
  2. File name:

    • Rename stac_cloud_theme.dartstac_theme_config.dart
  3. Export statement in packages/stac_core/lib/annotations/annotations.dart:

    • export 'stac_cloud_theme.dart';export 'stac_theme_config.dart';
  4. Documentation example (which also needs fixing for the wrong name):

    • @StacThemeAnnotation(themeName: 'darkTheme')@StacThemeConfig(themeName: 'darkTheme')

Would you like to make these changes, or should I provide the complete code blocks for each update?

Comment on lines +54 to 53
StacArtifactType.theme: {},
};

/// Fetches an artifact from Stac Cloud with intelligent caching.
///
Copy link
Contributor

@coderabbitai coderabbitai bot Dec 11, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix _backgroundFetchInProgress value literals (currently map literals, not sets)

Right now the values in _backgroundFetchInProgress use {}, which is a Map literal in Dart, but the field type is Map<StacArtifactType, Set<String>>. This won’t compile:

static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
  StacArtifactType.screen: {},
  StacArtifactType.theme: {},
};

You need set literals here:

-  static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
-    StacArtifactType.screen: {},
-    StacArtifactType.theme: {},
-  };
+  static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
+    StacArtifactType.screen: <String>{},
+    StacArtifactType.theme: <String>{},
+  };

Given _fetchAndUpdateArtifactInBackground does _backgroundFetchInProgress[artifactType]!, any missing key will also throw at runtime; if you expect more artifact types later, consider building this map from StacArtifactType.values to stay future-proof.

Also applies to: 337-378

🤖 Prompt for AI Agents
packages/stac/lib/src/services/stac_cloud.dart lines 54-58 (and similarly lines
337-378): the map is typed as Map<StacArtifactType, Set<String>> but uses {}
which creates Map literals and not Set<String> — replace the value literals with
explicit set literals like <String>{} (e.g. StacArtifactType.screen: <String>{},
StacArtifactType.theme: <String>{}); to be future-proof you can build the map
from StacArtifactType.values (e.g.
Map.fromEntries(StacArtifactType.values.map((t) => MapEntry(t, <String>{})))) or
ensure every enum key is initialized so
_backgroundFetchInProgress[artifactType]! never returns null (alternatively use
putIfAbsent before accessing).

Copy link
Member

Choose a reason for hiding this comment

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

@Potatomonsta Can we check this?

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment on lines 6 to 12
/// Example usage:
/// ```dart
/// @StacThemeAnnotation(themeName: 'darkTheme')
/// ThemeData buildDarkTheme() {
/// return ThemeData.dark();
/// }
/// ```
Copy link
Member

Choose a reason for hiding this comment

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

Can we name it StacThemeConfig?

///
/// Example usage:
/// ```dart
/// @StacThemeAnnotation(themeName: 'darkTheme')
Copy link
Member

Choose a reason for hiding this comment

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

Wrong example here.

/// Gets a cached artifact by its name and type.
///
/// Returns `null` if the screen is not cached.
static Future<StacScreenCache?> getCachedScreen(String screenName) async {
Copy link
Member

Choose a reason for hiding this comment

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

I think we should also rename StacScreenCache model to StacCache


/// Gets a cached screen by its name.
/// Gets the cache prefix for a given artifact type.
static String _getCachePrefix(String artifactType) {
Copy link
Member

Choose a reason for hiding this comment

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

Can we make artifactType as enum?

Copy link
Member

Choose a reason for hiding this comment

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

I see there is already an enum for StacArtifactType, can we use that?

Comment on lines 118 to 126
static Future<bool> isCacheValid({
required String artifactName,
required String artifactType,
Duration? maxAge,
}) async {
final cachedArtifact = await getCachedArtifact(artifactName, artifactType);
return isCacheValidSync(cachedArtifact, maxAge);
}

Copy link
Member

Choose a reason for hiding this comment

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

Are we using this function anywhere?

return null;
}

final themePayload = _normalizeThemeJson(rawData['stacJson']);
Copy link
Member

Choose a reason for hiding this comment

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

Please change the name of _normalizeThemeJson 😆

Add comprehensive theme support to the Stac framework, enabling themes to be loaded from Stac Cloud similar to screens. This includes:

- Add StacArtifactType enum to support both screens and themes
- Add StacCloudTheme annotation for marking theme builder methods
- Add StacAppTheme helper class for loading themes from cloud, network, or JSON
- Extend StacApp to support FutureOr<StacTheme?> for async theme loading
- Refactor StacCacheService to be artifact-type agnostic (supports screens and themes)
- Refactor StacCloud to support fetching both screens and themes with unified caching

Themes can now be loaded asynchronously from Stac Cloud using the same caching strategies as screens (optimistic, cache-first, network-first, etc.), with proper loading states handled in StacApp.
fixes for PR review comments
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/stac/lib/src/models/stac_artifact_cache.dart (1)

66-69: toString() still references the old class name.

The output says StacScreenCache(...) but the class is now StacArtifactCache.

🔎 Suggested fix
   @override
   String toString() {
-    return 'StacScreenCache(name: $name, version: $version, cachedAt: $cachedAt)';
+    return 'StacArtifactCache(name: $name, version: $version, cachedAt: $cachedAt)';
   }
♻️ Duplicate comments (4)
packages/stac/lib/src/services/stac_cloud.dart (2)

47-50: Use explicit set literals for clarity and safety.

While Dart may infer {} as Set<String> from the map's value type, using explicit <String>{} prevents ambiguity and ensures correctness if the context changes. Additionally, consider building from StacArtifactType.values to stay future-proof when new artifact types are added.

🔎 Suggested fix
-  static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
-    StacArtifactType.screen: {},
-    StacArtifactType.theme: {},
-  };
+  static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
+    for (final type in StacArtifactType.values) type: <String>{},
+  };

332-334: Force-unwrap on map access may throw if a new artifact type is added.

_backgroundFetchInProgress[artifactType]! assumes all enum values exist as keys. If a new StacArtifactType is added without updating the map initializer, this will throw at runtime. Use putIfAbsent or the suggested map comprehension to guarantee all enum values are present.

🔎 Defensive access pattern
-    final inProgressSet = _backgroundFetchInProgress[artifactType]!;
+    final inProgressSet = _backgroundFetchInProgress.putIfAbsent(
+      artifactType,
+      () => <String>{},
+    );
packages/stac/lib/src/framework/stac_app.dart (2)

283-307: Future is recreated on every widget rebuild.

_resolveThemes() is invoked during build(), creating a new Future instance on each rebuild. Since FutureBuilder uses reference equality, this causes the async operation to restart and the loading indicator to reappear whenever StacApp rebuilds.

Convert StacApp to a StatefulWidget and cache the resolved future in initState().

🔎 Suggested refactor to StatefulWidget
-class StacApp extends StatelessWidget {
+class StacApp extends StatefulWidget {
   const StacApp({
     super.key,
     // ... existing parameters
   });
+
+  @override
+  State<StacApp> createState() => _StacAppState();
+}
+
+class _StacAppState extends State<StacApp> {
+  late final FutureOr<_ResolvedStacThemes> _resolvedThemes;
+
+  @override
+  void initState() {
+    super.initState();
+    _resolvedThemes = _resolveThemes();
+  }
+
+  // Move _resolveThemes and build logic here, referencing widget.theme, widget.darkTheme

295-300: Error state shows perpetual loading indicator.

When theme loading fails, the same _ThemeFutureLoading widget is returned, leaving users stuck. Log the error and fall back to null themes so the app remains usable.

🔎 Suggested fix
           if (snapshot.hasError) {
+            debugPrint('Theme loading failed: ${snapshot.error}');
-            return const _ThemeFutureLoading();
+            return builder(futureContext, const _ResolvedStacThemes(theme: null, darkTheme: null));
           }
🧹 Nitpick comments (7)
packages/stac_core/lib/annotations/stac_theme_config.dart (1)

13-19: Consider documenting theme name constraints and uniqueness.

While the implementation is correct, the documentation could be enhanced to clarify:

  • Whether themeName values must be unique across the application
  • Any naming conventions or restrictions for themeName (e.g., non-empty, no special characters)
  • Whether annotated methods should be static or instance methods

These clarifications would help developers use the annotation correctly without needing to reference external documentation.

packages/stac/lib/src/framework/stac_app.dart (1)

270-280: Resolve theme futures in parallel.

Currently themeFuture and darkThemeFuture are awaited sequentially. Use Future.wait to resolve them concurrently, reducing total loading time when both are async.

🔎 Parallel resolution
     return Future<_ResolvedStacThemes>(() async {
-      final resolvedTheme =
-          await (themeFuture ?? Future<StacTheme?>.value(themeValue));
-      final resolvedDarkTheme =
-          await (darkThemeFuture ?? Future<StacTheme?>.value(darkThemeValue));
+      final results = await Future.wait([
+        themeFuture ?? Future<StacTheme?>.value(themeValue),
+        darkThemeFuture ?? Future<StacTheme?>.value(darkThemeValue),
+      ]);
+      final resolvedTheme = results[0];
+      final resolvedDarkTheme = results[1];

       return _ResolvedStacThemes(
         theme: resolvedTheme,
         darkTheme: resolvedDarkTheme,
       );
     });
packages/stac/lib/src/models/stac_artifact_cache.dart (1)

7-9: Update documentation to reflect the artifact-centric rename.

The doc comment still references "cached screen" but the class now represents any artifact type (screens, themes, etc.).

🔎 Suggested fix
-/// Model representing a cached screen from Stac Cloud.
+/// Model representing a cached artifact from Stac Cloud.
 ///
-/// This model stores the screen data along with metadata for caching purposes.
+/// This model stores artifact data (screens, themes, etc.) along with metadata for caching purposes.
packages/stac/lib/src/services/stac_cache_service.dart (4)

44-46: Add logging when no cached data is found.

As previously requested, logging helps with debugging cache misses.

🔎 Suggested fix
       if (cachedData == null) {
+        Log.d('No cached data found for ${artifactType.name} $artifactName');
         return null;
       }

79-81: Add logging for cache save failures.

Silent failures make debugging difficult. Log the error before returning false.

🔎 Suggested fix
     } catch (e) {
+      Log.w('Failed to save artifact $name (${artifactType.name}): $e');
       return false;
     }

93-97: Add logging for cache removal failures.

Consistent with getCachedArtifact, log errors when removal fails.

🔎 Suggested fix
     } catch (e) {
+      Log.w('Failed to remove artifact $artifactName (${artifactType.name}): $e');
       return false;
     }

109-113: Add logging for cache clear failures.

Log errors when clearing all artifacts of a type fails.

🔎 Suggested fix
     } catch (e) {
+      Log.w('Failed to clear all ${artifactType.name} artifacts: $e');
       return false;
     }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 43ffe4f and 3538b9f.

📒 Files selected for processing (11)
  • packages/stac/lib/src/framework/framework.dart
  • packages/stac/lib/src/framework/stac_app.dart
  • packages/stac/lib/src/framework/stac_app_theme.dart
  • packages/stac/lib/src/models/models.dart
  • packages/stac/lib/src/models/stac_artifact_cache.dart
  • packages/stac/lib/src/models/stac_artifact_cache.g.dart
  • packages/stac/lib/src/models/stac_artifact_type.dart
  • packages/stac/lib/src/services/stac_cache_service.dart
  • packages/stac/lib/src/services/stac_cloud.dart
  • packages/stac_core/lib/annotations/annotations.dart
  • packages/stac_core/lib/annotations/stac_theme_config.dart
🚧 Files skipped from review as they are similar to previous changes (3)
  • packages/stac_core/lib/annotations/annotations.dart
  • packages/stac/lib/src/framework/stac_app_theme.dart
  • packages/stac/lib/src/models/stac_artifact_type.dart
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-19T20:20:55.561Z
Learnt from: akhil-ge0rge
Repo: StacDev/stac PR: 399
File: packages/stac/lib/src/parsers/theme/stac_tool_tip_theme_data_parser.dart:9-26
Timestamp: 2025-12-19T20:20:55.561Z
Learning: In Flutter, TooltipThemeData.height is deprecated since v3.30.0-0.1.pre. Replace uses with TooltipThemeData.constraints: BoxConstraints(minHeight: value) to achieve the same minimum height. Apply this pattern to review any Dart/Flutter code that uses TooltipThemeData.height, not just this file.

Applied to files:

  • packages/stac/lib/src/framework/framework.dart
  • packages/stac_core/lib/annotations/stac_theme_config.dart
  • packages/stac/lib/src/services/stac_cache_service.dart
  • packages/stac/lib/src/models/models.dart
  • packages/stac/lib/src/models/stac_artifact_cache.g.dart
  • packages/stac/lib/src/models/stac_artifact_cache.dart
  • packages/stac/lib/src/framework/stac_app.dart
  • packages/stac/lib/src/services/stac_cloud.dart
🔇 Additional comments (4)
packages/stac_core/lib/annotations/stac_theme_config.dart (1)

1-12: Previous issues addressed correctly.

The annotation name in the documentation example now correctly uses @StacThemeConfig, and the rename from StacCloudTheme to StacThemeConfig has been implemented as requested in the previous review.

packages/stac/lib/src/framework/framework.dart (1)

5-5: LGTM!

The new export for stac_app_theme.dart correctly exposes the theme loading utilities as part of the framework's public API.

packages/stac/lib/src/models/stac_artifact_cache.g.dart (1)

1-23: LGTM!

Generated serialization code correctly reflects the renamed StacArtifactCache model with proper field mappings.

packages/stac/lib/src/services/stac_cloud.dart (1)

23-24: Verify hardcoded base URL is appropriate.

The base URL points to a Firebase Cloud Functions endpoint. Confirm this is intentional for production or consider making it configurable (e.g., via StacService.options).

Comment on lines +7 to +11
/// ```dart
/// @StacThemeConfig(themeName: 'darkTheme')
/// ThemeData buildDarkTheme() {
/// return ThemeData.dark();
/// }
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Search for StacTheme type definition and usage patterns to confirm expected return type for theme builder methods

echo "=== Searching for StacTheme type definition ==="
rg -n "class StacTheme" --type dart -C 3

echo -e "\n=== Searching for theme builder method patterns ==="
rg -n "@StacThemeConfig" --type dart -A 5

echo -e "\n=== Searching for StacAppTheme usage to understand theme type expectations ==="
rg -n "StacAppTheme\.(fromCloud|fromNetwork|fromJson)" --type dart -C 3

Repository: StacDev/stac

Length of output: 4233


🏁 Script executed:

#!/bin/bash
# Read the full annotation file to understand the expected return type
echo "=== Full content of stac_theme_config.dart ==="
cat -n packages/stac_core/lib/annotations/stac_theme_config.dart

echo -e "\n=== Search for actual @StacThemeConfig decorator usage (excluding comments) ==="
rg -n "@StacThemeConfig" --type dart -B 3 -A 10

echo -e "\n=== Check imports in the annotation file ==="
head -n 20 packages/stac_core/lib/annotations/stac_theme_config.dart

Repository: StacDev/stac

Length of output: 2704


🏁 Script executed:

#!/bin/bash
# Search for how themes are built and registered
echo "=== Search for theme builder or theme registration patterns ==="
rg -n "buildTheme|buildDark|buildLight|themeBuilder" --type dart -C 2

echo -e "\n=== Search for code that processes @StacThemeConfig ==="
rg -n "StacThemeConfig|themeRegistry|registerTheme" --type dart -C 2

echo -e "\n=== Look at StacTheme to understand its structure ==="
head -n 100 packages/stac_core/lib/foundation/theme/stac_theme/stac_theme.dart

Repository: StacDev/stac

Length of output: 6479


🏁 Script executed:

#!/bin/bash
# Check if there's any conversion between ThemeData and StacTheme
echo "=== Search for ThemeData to StacTheme conversion or relationship ==="
rg -n "toThemeData|fromThemeData|ThemeData" packages/stac_core/lib/foundation/theme/stac_theme/ --type dart -C 1

echo -e "\n=== Check if StacTheme has a method to convert to ThemeData ==="
grep -n "ThemeData" packages/stac_core/lib/foundation/theme/stac_theme/stac_theme.dart | head -20

echo -e "\n=== Look for any documentation or README about themes ==="
find packages/stac_core -name "README*" -o -name "*.md" | xargs grep -l -i "theme" 2>/dev/null || echo "No theme-related docs found"

echo -e "\n=== Check the structure of StacTheme more - look for conversion methods ==="
sed -n '1,150p' packages/stac_core/lib/foundation/theme/stac_theme/stac_theme.dart | tail -50

Repository: StacDev/stac

Length of output: 24269


Correct the documented example return type for @StacThemeConfig methods.

Theme builder methods annotated with @StacThemeConfig should return StacTheme, not ThemeData. The framework exclusively uses Stac model types (@JsonSerializable() classes) for theme configuration. Update the example to:

@StacThemeConfig(themeName: 'darkTheme')
StacTheme buildDarkTheme() {
  return StacTheme(...);
}

Also update the annotation's documentation to clarify the expected return type explicitly.

🤖 Prompt for AI Agents
In packages/stac_core/lib/annotations/stac_theme_config.dart around lines 7 to
11, the documentation example shows theme builder methods returning ThemeData
but the framework expects StacTheme; update the example return type and body to
use StacTheme instead of ThemeData and change the annotation doc text to
explicitly state annotated methods must return StacTheme (a @JsonSerializable()
Stac model) so callers and docs are consistent.

@@ -1,2 +1,2 @@
export 'package:stac/src/models/stac_cache_config.dart';
export 'package:stac/src/models/stac_screen_cache.dart';
export 'package:stac/src/models/stac_artifact_cache.dart';
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

rg -n 'StacScreenCache' --type dart

Repository: StacDev/stac

Length of output: 195


🏁 Script executed:

cat -n packages/stac/lib/src/models/stac_artifact_cache.dart | sed -n '60,75p'

Repository: StacDev/stac

Length of output: 561


Update the toString() method to reflect the renamed class.

The export change itself is not a breaking change—no external references to StacScreenCache exist in the codebase. However, the toString() method in stac_artifact_cache.dart line 68 still outputs the old class name 'StacScreenCache(...)' instead of 'StacArtifactCache(...)'. Update the string to match the renamed class.

🤖 Prompt for AI Agents
In packages/stac/lib/src/models/stac_artifact_cache.dart around line 68, the
toString() method still returns the old class name 'StacScreenCache(...)';
update that literal to 'StacArtifactCache(...)' so the printed representation
matches the renamed class, and run tests/lint to confirm no other string
references remain.

@divyanshub024
Copy link
Member

divyanshub024 commented Dec 24, 2025

Currently, we need to import all these files.

import 'package:stac_core/annotations/stac_theme_config.dart';
import 'package:stac_core/foundation/colors/stac_brightness.dart';
import 'package:stac_core/foundation/theme/stac_color_scheme/stac_color_scheme.dart';
import 'package:stac_core/foundation/theme/stac_theme/stac_theme.dart';

Can we just import the stac_core/stac_core.dart

/// return ThemeData.dark();
/// }
/// ```
class StacThemeConfig {
Copy link
Member

Choose a reason for hiding this comment

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

StacThemeRef

@divyanshub024
Copy link
Member

Add docs and example in movie app

- Introduced a new "Theming" concept in the documentation, detailing how to implement and manage themes in Stac applications.
- Updated the main application example to utilize cloud-based themes instead of local theme definitions.
- Removed the deprecated app_theme.dart file to streamline theme management.
- Enhanced StacApp to support various theme loading methods, including cloud, network, and JSON sources.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
packages/stac_core/lib/annotations/stac_theme_ref.dart (1)

6-12: Documentation example has inconsistencies.

The example code shows @StacThemeConfig but the actual class is StacThemeRef. Additionally, the return type should be StacTheme, not ThemeData, as confirmed by the usage in examples/movie_app/stac/app_theme.dart.

🔎 Proposed fix
 /// Example usage:
 /// ```dart
-/// @StacThemeConfig(themeName: 'darkTheme')
-/// ThemeData buildDarkTheme() {
-///   return ThemeData.dark();
+/// @StacThemeRef(name: 'darkTheme')
+/// StacTheme get darkTheme => StacTheme(
+///   brightness: StacBrightness.dark,
+///   // ... theme configuration
+/// );
 /// }
 /// ```
packages/stac/lib/src/framework/stac_app.dart (1)

275-303: Future is recreated on every widget rebuild.

The _resolveThemes() method is called inside _withResolvedThemes during build(). Since StacApp is a StatelessWidget, each rebuild creates a new Future object. FutureBuilder uses reference equality, so it will restart the async operation and show the loading state again whenever StacApp rebuilds (e.g., when a parent rebuilds).

Consider converting StacApp to a StatefulWidget and caching the resolved future in initState().

🔎 Proposed approach
-class StacApp extends StatelessWidget {
+class StacApp extends StatefulWidget {
   const StacApp({
     // ... existing constructor params
   });
+
+  @override
+  State<StacApp> createState() => _StacAppState();
+}
+
+class _StacAppState extends State<StacApp> {
+  late final Future<_ResolvedStacThemes> _resolvedThemes;
+
+  @override
+  void initState() {
+    super.initState();
+    _resolvedThemes = _resolveThemes();
+  }
+
+  // Use _resolvedThemes in build instead of calling _resolveThemes()
🧹 Nitpick comments (2)
packages/stac/lib/src/framework/stac_app.dart (2)

249-273: Consider resolving themes in parallel.

The theme resolution awaits each theme sequentially. Since themeFuture and darkThemeFuture are independent, resolving them in parallel with Future.wait would improve loading performance.

🔎 Proposed optimization
   return Future<_ResolvedStacThemes>(() async {
-    final resolvedTheme =
-        await (themeFuture ?? Future<StacTheme?>.value(null));
-    final resolvedDarkTheme =
-        await (darkThemeFuture ?? Future<StacTheme?>.value(null));
+    final results = await Future.wait([
+      themeFuture ?? Future<StacTheme?>.value(null),
+      darkThemeFuture ?? Future<StacTheme?>.value(null),
+    ]);

     return _ResolvedStacThemes(
-      theme: resolvedTheme,
-      darkTheme: resolvedDarkTheme,
+      theme: results[0],
+      darkTheme: results[1],
     );
   });

313-320: Consider customizable loading indicator.

The _ThemeFutureLoading widget shows a plain CircularProgressIndicator. For better UX, consider allowing customization of the loading widget or using a minimal blank screen to avoid visual jarring.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3538b9f and 2e1a97f.

📒 Files selected for processing (10)
  • docs/concepts/theming.mdx
  • docs/docs.json
  • examples/movie_app/lib/main.dart
  • examples/movie_app/lib/themes/app_theme.dart
  • examples/movie_app/stac/app_theme.dart
  • examples/stac_gallery/lib/main.dart
  • packages/stac/lib/src/framework/stac_app.dart
  • packages/stac/lib/src/framework/stac_app_theme.dart
  • packages/stac_core/lib/annotations/annotations.dart
  • packages/stac_core/lib/annotations/stac_theme_ref.dart
💤 Files with no reviewable changes (1)
  • examples/movie_app/lib/themes/app_theme.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/stac/lib/src/framework/stac_app_theme.dart
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-19T20:20:55.561Z
Learnt from: akhil-ge0rge
Repo: StacDev/stac PR: 399
File: packages/stac/lib/src/parsers/theme/stac_tool_tip_theme_data_parser.dart:9-26
Timestamp: 2025-12-19T20:20:55.561Z
Learning: In Flutter, TooltipThemeData.height is deprecated since v3.30.0-0.1.pre. Replace uses with TooltipThemeData.constraints: BoxConstraints(minHeight: value) to achieve the same minimum height. Apply this pattern to review any Dart/Flutter code that uses TooltipThemeData.height, not just this file.

Applied to files:

  • packages/stac_core/lib/annotations/stac_theme_ref.dart
  • examples/stac_gallery/lib/main.dart
  • examples/movie_app/stac/app_theme.dart
  • examples/movie_app/lib/main.dart
  • packages/stac/lib/src/framework/stac_app.dart
  • packages/stac_core/lib/annotations/annotations.dart
🪛 LanguageTool
docs/concepts/theming.mdx

[style] ~279-~279: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... use @StacThemeRef:** - Only when you want to generate JSON and deploy themes to Stac...

(REP_WANT_TO_VB)

🔇 Additional comments (11)
examples/movie_app/lib/main.dart (1)

37-37: LGTM!

The migration to StacAppTheme(name: "movie_app_dark") correctly uses the new cloud-based theming API. The theme name matches the @StacThemeRef annotation defined in stac/app_theme.dart.

packages/stac_core/lib/annotations/annotations.dart (1)

4-4: LGTM!

The export of stac_theme_ref.dart follows the existing pattern and correctly exposes the new StacThemeRef annotation through the barrel file.

docs/docs.json (1)

43-44: LGTM!

The new theming documentation page is correctly added to the navigation structure under the Concepts group.

examples/movie_app/stac/app_theme.dart (3)

1-19: Well-structured theme definition.

The theme is cleanly organized with the @StacThemeRef annotation for cloud deployment. The separation of concerns with helper methods (_buildTheme, _buildTextTheme, etc.) follows best practices and aligns with the documentation examples.


35-113: Comprehensive text theme implementation.

The text theme covers all Material Design text styles with consistent patterns. Good use of the _textStyle helper to reduce duplication.


115-158: Button and utility helpers look good.

The button styles and helper functions are well-implemented with appropriate defaults.

examples/stac_gallery/lib/main.dart (2)

5-5: Import simplified to barrel file.

This change aligns with the reviewer feedback requesting simplified imports through a single barrel import.


44-45: Correct usage of StacAppTheme.json API.

The migration to StacAppTheme.json(payload: ...) properly wraps the theme payloads from the cubit state using the new theming API.

docs/concepts/theming.mdx (2)

1-116: Comprehensive theming documentation.

The documentation thoroughly covers all four theme sources with clear examples and workflow explanations. The Cloud Themes section with the CLI deployment workflow is particularly helpful.


397-415: Good best practices and error handling sections.

The guidance on when to use each theme source and the error handling behavior documentation are valuable additions.

packages/stac/lib/src/framework/stac_app.dart (1)

286-292: Error handling improvement looks good.

The error handling now properly logs a warning and falls back to null themes instead of showing a perpetual loading state. This addresses the previously flagged concern.

Comment on lines 179 to 198
Helper functions (shared for both approaches):

```dart
StacTheme _buildTheme({
required StacBrightness brightness,
required StacColorScheme colorScheme,
}) {
return StacTheme(
brightness: brightness,
colorScheme: colorScheme,
textTheme: _buildTextTheme(),
filledButtonTheme: _buildFilledButtonTheme(),
outlinedButtonTheme: _buildOutlinedButtonTheme(),
dividerTheme: _buildDividerTheme(),
);
}
```

Helper functions (shared for both approaches):

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Duplicate section header.

There's a duplicate "Helper functions (shared for both approaches):" label on lines 179 and 197.

🔎 Proposed fix

Remove the duplicate header on line 197:

 }

-Helper functions (shared for both approaches):

StacTextTheme _buildTextTheme() {
🤖 Prompt for AI Agents
In docs/concepts/theming.mdx around lines 179 to 198, there's a duplicated
section header "Helper functions (shared for both approaches):" appearing at
lines 179 and 197; remove the duplicate header at line 197 so only the first
occurrence remains and ensure surrounding markdown/code spacing remains correct
(delete the redundant header line and any stray blank line introduced).

@divyanshub024
Copy link
Member

@Potatomonsta PR checks are failing, please check

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2e1a97f and ae50bfd.

⛔ Files ignored due to path filters (1)
  • examples/movie_app/pubspec.lock is excluded by !**/*.lock
📒 Files selected for processing (1)
  • pubspec.yaml

pubspec.yaml Outdated

dev_dependencies:
melos: ^6.3.2
melos: ^7.3.0
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

melos 7.3.0 breaking changes from version 6 dart flutter

💡 Result:

Summary of breaking changes when upgrading from Melos 6.x → 7.3.0:

  1. Melos now uses Dart/pub workspaces (migration from Melos-managed overrides). This is a breaking change: Melos no longer writes/relies on pubspec_overrides.yaml; workspaces are used instead. [1][2]

  2. melos.yaml removed — workspace configuration moves into the root pubspec.yaml (root becomes a workspace package). You must migrate settings from melos.yaml into the root pubspec.yaml. [1][2]

  3. You must list your packages in the root pubspec.yaml workspace section (a packages list) and add resolution: workspace to each package that should resolve to workspace packages. (i.e., add workspace: { packages: [...] } in root pubspec and resolution: workspace in child pubspecs). [2][0]

  4. Some command behavior changed to operate at the workspace root (e.g., pub get runs in the workspace root). Review bootstrap/exec behavior after migrating. [1][2]

  5. Miscellaneous non-breaking features in 7.3.0: groups config for scripts and an option to opt-out of IntelliJ runConfiguration generation (not breaking but new features to be aware of). [1]

Sources:

  • Melos changelog (pub.dev) — 7.0.0 → 7.3.0 notes. [1]
  • Community migration instructions / discussion (migration details & examples). [2]

References:
[1] Melos changelog (pub.dev).
[2] Melos workspace migration discussion / example (forum).


🏁 Script executed:

# Search for CI configuration files
find . -type f \( -name "*.yml" -o -name "*.yaml" \) | grep -E "(\.github|\.gitlab|\.cirrus|workflow)" | head -20

Repository: StacDev/stac

Length of output: 150


🏁 Script executed:

# Search for melos configuration
find . -type f -name "melos.yaml" -o -name "pubspec.yaml" | xargs grep -l "melos" 2>/dev/null

Repository: StacDev/stac

Length of output: 70


🏁 Script executed:

# Check git commit history or PR description to understand CI failures context
git log --oneline -20 2>/dev/null || echo "No git history available"

Repository: StacDev/stac

Length of output: 85


🏁 Script executed:

# Check if melos.yaml exists
fd "melos\.yaml" . 2>/dev/null && echo "melos.yaml found" || echo "melos.yaml not found"

Repository: StacDev/stac

Length of output: 85


🏁 Script executed:

# Check if pubspec_overrides.yaml exists
fd "pubspec_overrides\.yaml" . 2>/dev/null && echo "pubspec_overrides.yaml found" || echo "pubspec_overrides.yaml not found"

Repository: StacDev/stac

Length of output: 355


🏁 Script executed:

# Read root pubspec.yaml to see if workspace configuration is present
head -50 ./pubspec.yaml

Repository: StacDev/stac

Length of output: 152


🏁 Script executed:

# Check the CI workflow file to understand what might be failing
cat ./.github/workflows/all_plugins.yaml

Repository: StacDev/stac

Length of output: 1879


🏁 Script executed:

# Search for any melos commands in scripts or CI files
rg "melos" . --type yaml --type json -A 2 2>/dev/null | head -50

Repository: StacDev/stac

Length of output: 1591


Melos v7.3.0 upgrade is incomplete and will cause CI failures.

This major version upgrade requires several breaking changes that have not been applied:

  1. Remove deprecated melos.yaml — Melos v7 no longer uses melos.yaml; configuration must migrate to the root pubspec.yaml.
  2. Remove/migrate pubspec_overrides.yaml — The 6 pubspec_overrides.yaml files (in packages and examples) are incompatible with v7's workspace system. These must be removed and replaced with workspace resolution configuration.
  3. Add workspace configuration to root pubspec.yaml — The root pubspec.yaml must include a workspace section listing all packages with resolution: workspace in each package's pubspec.
  4. Update CI workflow — The .github/workflows/all_plugins.yaml uses a pinned melos-action v3.0.1, which predates v7 and will not work with the upgraded dependency. Update the melos-version to match ^7.3.0.

These missing migration steps are likely causing the failing CI checks.

🤖 Prompt for AI Agents
In pubspec.yaml around line 7, the Melos v7 upgrade is incomplete and will break
CI: remove deprecated melos.yaml from the repo, delete the six
pubspec_overrides.yaml files in packages/ and examples/ (they are incompatible
with Melos v7), add a workspace section to the root pubspec.yaml that lists
every package path and ensure each package's pubspec.yaml sets resolution:
workspace for workspace dependency resolution, and update
.github/workflows/all_plugins.yaml to set melos-version to ^7.3.0 (or a specific
7.x tag) so the CI melos-action matches the upgraded Melos dependency.

final StacTheme? darkTheme;
}

class _ThemeFutureLoading extends StatelessWidget {
Copy link
Member

Choose a reason for hiding this comment

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

Rename this to _LoadingWidget

Comment on lines 306 to 312
class _ResolvedStacThemes {
const _ResolvedStacThemes({required this.theme, required this.darkTheme});

final StacTheme? theme;
final StacTheme? darkTheme;
}

Copy link
Member

Choose a reason for hiding this comment

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

Can we make this as a record?
typedef ResolvedStacThemes = ({StacTheme? theme, StacTheme? darkTheme});

Future<StacTheme?> resolve() async {
switch (_source) {
case _ThemeSource.dsl:
return Future.value(_dslTheme);
Copy link
Member

Choose a reason for hiding this comment

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

Do we need Future.Value? We can return _dslTheme directly.

final _ThemeSource _source;
final BuildContext? _context;
final StacNetworkRequest? _request;
final dynamic _jsonPayload;
Copy link
Member

Choose a reason for hiding this comment

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

Can we use Object? for slightly more type safety, since dynamic disables static analysis.

class StacScreenCache {
/// Creates a [StacScreenCache] instance.
const StacScreenCache({
class StacArtifactCache {
Copy link
Member

Choose a reason for hiding this comment

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

Can we rename this to StacCache?


@override
String toString() {
return 'StacScreenCache(name: $name, version: $version, cachedAt: $cachedAt)';
Copy link
Member

Choose a reason for hiding this comment

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

Wrong class name

Comment on lines +54 to 53
StacArtifactType.theme: {},
};

/// Fetches an artifact from Stac Cloud with intelligent caching.
///
Copy link
Member

Choose a reason for hiding this comment

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

@Potatomonsta Can we check this?

///
/// Example usage:
/// ```dart
/// @StacThemeConfig(themeName: 'darkTheme')
Copy link
Member

Choose a reason for hiding this comment

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

Wrong Class name

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
packages/stac/lib/src/models/stac_artifact_cache.g.dart (1)

1-23: Run the code generator to fix formatting issues.

The pipeline is reporting formatting issues in this generated file. Since this is a generated file, you should regenerate it using the build_runner to ensure proper formatting.

Run the following to regenerate:

#!/bin/bash
# Regenerate the file and format it
melos run build_runner
melos run format
packages/stac/lib/src/services/stac_cache_service.dart (1)

1-131: Run formatter to fix formatting issues.

The pipeline is reporting formatting issues in this file. Please run the formatter to ensure code style compliance.

Run the following command:

#!/bin/bash
# Format the file
melos run format
♻️ Duplicate comments (1)
packages/stac/lib/src/services/stac_cloud.dart (1)

47-50: Critical: Fix Set literal syntax in _backgroundFetchInProgress initialization.

The map values use {} which creates Map literals, not Set<String> literals. This will cause type errors at runtime when trying to call Set methods like add() and remove().

🔎 Fix the literal syntax
   static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
-    StacArtifactType.screen: {},
-    StacArtifactType.theme: {},
+    StacArtifactType.screen: <String>{},
+    StacArtifactType.theme: <String>{},
   };

Alternatively, for future-proofing against new artifact types:

-  static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
-    StacArtifactType.screen: {},
-    StacArtifactType.theme: {},
-  };
+  static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress =
+    Map.fromEntries(
+      StacArtifactType.values.map((type) => MapEntry(type, <String>{})),
+    );

Based on past review comments, this issue was previously flagged but remains unresolved.

🧹 Nitpick comments (1)
packages/stac/lib/src/framework/stac_app.dart (1)

291-291: Consider using a more descriptive typedef name.

The typedef _ResolvedStacThemes uses a record type, which is good. However, since this is now addressing a past comment that suggested making it a record, you might consider if the name clearly conveys that it's a resolved/awaited result.

Based on past review comments requesting record usage, this implementation looks good.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e98d2c and 1b077a1.

📒 Files selected for processing (6)
  • packages/stac/lib/src/framework/stac_app.dart
  • packages/stac/lib/src/framework/stac_app_theme.dart
  • packages/stac/lib/src/models/stac_artifact_cache.dart
  • packages/stac/lib/src/models/stac_artifact_cache.g.dart
  • packages/stac/lib/src/services/stac_cache_service.dart
  • packages/stac/lib/src/services/stac_cloud.dart
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-19T20:20:55.561Z
Learnt from: akhil-ge0rge
Repo: StacDev/stac PR: 399
File: packages/stac/lib/src/parsers/theme/stac_tool_tip_theme_data_parser.dart:9-26
Timestamp: 2025-12-19T20:20:55.561Z
Learning: In Flutter, TooltipThemeData.height is deprecated since v3.30.0-0.1.pre. Replace uses with TooltipThemeData.constraints: BoxConstraints(minHeight: value) to achieve the same minimum height. Apply this pattern to review any Dart/Flutter code that uses TooltipThemeData.height, not just this file.

Applied to files:

  • packages/stac/lib/src/services/stac_cloud.dart
  • packages/stac/lib/src/framework/stac_app.dart
  • packages/stac/lib/src/framework/stac_app_theme.dart
  • packages/stac/lib/src/services/stac_cache_service.dart
  • packages/stac/lib/src/models/stac_artifact_cache.g.dart
  • packages/stac/lib/src/models/stac_artifact_cache.dart
🪛 GitHub Actions: all_plugins
packages/stac/lib/src/services/stac_cache_service.dart

[error] 1-1: Formatting issues detected by melos format. The file 'stac_cache_service.dart' is not formatted correctly. See diff output from the formatter.

packages/stac/lib/src/models/stac_artifact_cache.g.dart

[error] 1-1: Formatting issues detected by melos format. The file 'stac_artifact_cache.g.dart' is not formatted correctly. See diff output from the formatter.

🔇 Additional comments (7)
packages/stac/lib/src/models/stac_artifact_cache.dart (1)

11-84: LGTM! Class rename completed successfully.

The rename from StacScreenCache to StacCache is complete and consistent throughout the file. All references (constructors, factories, methods, documentation) have been properly updated to reflect the more generic artifact-centric naming.

packages/stac/lib/src/framework/stac_app.dart (1)

6-97: LGTM! StatefulWidget conversion and async theme resolution implemented correctly.

The migration from StatelessWidget to StatefulWidget is well-executed:

  • Theme resolution happens once in initState (avoiding rebuild issues)
  • Proper mounted checks before calling setState
  • Error handling with logging and fallback to null themes
  • Loading state properly managed

This implementation addresses all the concerns raised in previous reviews.

Also applies to: 142-178

packages/stac/lib/src/services/stac_cache_service.dart (1)

34-55: LGTM! Artifact-type aware caching implemented correctly.

The refactoring to support multiple artifact types (screens, themes) is well-implemented:

  • Clear separation via _getCachePrefix helper
  • Type-safe with StacArtifactType enum
  • Proper error logging with artifact type context
  • Consistent null returns on failures
packages/stac/lib/src/framework/stac_app_theme.dart (2)

26-89: LGTM! Multi-source theme loading implemented well.

The StacAppTheme wrapper provides a clean API for loading themes from multiple sources:

  • Clear constructor separation (dsl, cloud, network, json)
  • Type-safe routing with private _ThemeSource enum
  • Consistent async resolution pattern
  • Proper null handling for optional sources

The implementation is intuitive and follows good Flutter patterns.


141-163: LGTM! JSON parsing error handling implemented correctly.

The _themeJsonDynamicToMap helper properly handles all the edge cases:

  • Nested stacJson unwrapping
  • Direct Map passthrough
  • String parsing with try-catch (addresses previous feedback)
  • Error logging on parse failures

This addresses the concern raised in past reviews about FormatException handling.

packages/stac/lib/src/services/stac_cloud.dart (2)

26-44: LGTM! Artifact-type aware cloud fetching implemented correctly.

The refactoring to support multiple artifact types is well-structured:

  • Clear URL and query parameter routing via helper methods
  • Consistent artifact-type parameter threading through all methods
  • Proper cache integration with type awareness
  • Good separation of concerns

The generalized _fetchArtifact approach with type-specific wrappers (fetchScreen, fetchTheme) provides a clean public API.

Also applies to: 61-140


371-411: LGTM! Theme-specific cache management methods added.

The new fetchTheme and theme cache clearing methods (clearThemeCache, clearAllThemeCache) mirror the existing screen methods, providing a consistent API surface for theme artifact management.

- Changed StacApp from StatelessWidget to StatefulWidget to manage theme resolution state.
- Introduced a loading state while themes are being resolved.
- Refactored theme resolution logic to improve clarity and efficiency.
- Updated StacArtifactCache to StacCache for consistency across caching models.
- Adjusted related methods and types to reflect the new StacCache naming convention.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
packages/stac/lib/src/services/stac_cloud.dart (1)

47-50: Critical: Fix Map initialization - values must be Set literals, not Map literals.

The field is typed as Map<StacArtifactType, Set<String>> but the values use {} which creates Map literals in Dart, not Set<String>. This will cause type errors at runtime when add or remove is called (e.g., line 334).

🔎 Proposed fix
   static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
-    StacArtifactType.screen: {},
-    StacArtifactType.theme: {},
+    StacArtifactType.screen: <String>{},
+    StacArtifactType.theme: <String>{},
   };

For future-proofing when more artifact types are added:

-  static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = {
-    StacArtifactType.screen: {},
-    StacArtifactType.theme: {},
-  };
+  static final Map<StacArtifactType, Set<String>> _backgroundFetchInProgress = 
+    Map.fromEntries(
+      StacArtifactType.values.map((type) => MapEntry(type, <String>{})),
+    );
🧹 Nitpick comments (5)
packages/stac/lib/src/framework/stac_app.dart (2)

142-166: Consider implementing didUpdateWidget to handle theme changes.

If the parent widget rebuilds with different theme or darkTheme values, the current implementation won't re-resolve the themes. The resolved themes will remain stale until the widget is completely recreated.

🔎 Proposed implementation
   @override
   void initState() {
     super.initState();
     _themesFuture = _resolveThemes();
     _themesFuture!
         .then((themes) {
           if (mounted) {
             setState(() {
               _resolvedThemes = themes;
             });
           }
         })
         .catchError((error) {
           if (mounted) {
             Log.w('Failed to resolve theme: $error');
             setState(() {
               _resolvedThemes = (theme: null, darkTheme: null);
             });
           }
         });
   }
+
+  @override
+  void didUpdateWidget(StacApp oldWidget) {
+    super.didUpdateWidget(oldWidget);
+    if (widget.theme != oldWidget.theme || widget.darkTheme != oldWidget.darkTheme) {
+      _themesFuture = _resolveThemes();
+      _themesFuture!
+          .then((themes) {
+            if (mounted) {
+              setState(() {
+                _resolvedThemes = themes;
+              });
+            }
+          })
+          .catchError((error) {
+            if (mounted) {
+              Log.w('Failed to resolve theme: $error');
+              setState(() {
+                _resolvedThemes = (theme: null, darkTheme: null);
+              });
+            }
+          });
+    }
+  }

267-289: Consider parallelizing theme resolution for better performance.

The current implementation awaits themeFuture and darkThemeFuture sequentially. If both themes are loaded from network or cloud, this doubles the loading time unnecessarily.

🔎 Proposed parallel resolution
   Future<_ResolvedStacThemes> _resolveThemes() {
     final themeInput = widget.theme;
     final darkThemeInput = widget.darkTheme;

     // Both themes are optional, so we need to handle null cases
     final Future<StacTheme?>? themeFuture = themeInput?.resolve();
     final Future<StacTheme?>? darkThemeFuture = darkThemeInput?.resolve();

     // If both are null, return immediately with null themes
     if (themeFuture == null && darkThemeFuture == null) {
       return Future.value((theme: null, darkTheme: null));
     }

-    return Future<_ResolvedStacThemes>(() async {
-      final resolvedTheme =
-          await (themeFuture ?? Future<StacTheme?>.value(null));
-      final resolvedDarkTheme =
-          await (darkThemeFuture ?? Future<StacTheme?>.value(null));
-
-      return (theme: resolvedTheme, darkTheme: resolvedDarkTheme);
-    });
+    // Resolve both themes in parallel
+    return Future.wait([
+      themeFuture ?? Future<StacTheme?>.value(null),
+      darkThemeFuture ?? Future<StacTheme?>.value(null),
+    ]).then((results) => (theme: results[0], darkTheme: results[1]));
   }
packages/stac/lib/src/framework/stac_app_theme.dart (2)

78-89: Consider wrapping DSL theme in Future.value for consistency.

The dsl case returns _dslTheme directly while other cases return Futures. While Dart's type system allows this, explicitly wrapping it improves clarity and makes the async contract more obvious.

   Future<StacTheme?> resolve() async {
     switch (_source) {
       case _ThemeSource.dsl:
-        return _dslTheme;
+        return Future.value(_dslTheme);
       case _ThemeSource.cloud:
         return fromCloud(themeName: name!);
       case _ThemeSource.network:
         return fromNetwork(context: _context!, request: _request!);
       case _ThemeSource.json:
         return fromJson(_jsonPayload);
     }
   }

141-163: Recursive stacJson unwrapping could be clearer.

Lines 145-147 recursively call _themeJsonDynamicToMap when a stacJson key is found. While this handles one common nesting pattern, the recursion depth is unbounded and could theoretically loop infinitely on circular structures (though unlikely in practice with JSON).

Consider either:

  1. Adding a depth parameter to limit recursion
  2. Unwrapping only once explicitly
  3. Adding a comment explaining the expected nesting pattern
Example with explicit single unwrap
  static Map<String, dynamic>? _themeJsonDynamicToMap(dynamic payload) {
    if (payload == null) {
      return null;
    }
    
    // Unwrap nested 'stacJson' key once if present
    if (payload is Map<String, dynamic>) {
      final nested = payload['stacJson'];
      if (nested != null) {
        payload = nested;
      }
    }
    
    if (payload is Map<String, dynamic>) {
      return payload;
    }
    
    if (payload is String) {
      try {
        final decoded = jsonDecode(payload);
        if (decoded is Map<String, dynamic>) {
          return decoded;
        }
      } catch (e) {
        Log.w('Unexpected error parsing theme JSON: $e');
        return null;
      }
    }
    return null;
  }
packages/stac/lib/src/services/stac_cache_service.dart (1)

60-113: Consider adding error logging for consistency.

The saveArtifact, removeArtifact, and clearAllArtifacts methods catch exceptions but return false without logging, unlike getCachedArtifact which logs warnings (lines 50-52). Adding logs would help diagnose issues.

   static Future<bool> saveArtifact({
     required String name,
     required String stacJson,
     required int version,
     required StacArtifactType artifactType,
   }) async {
     try {
       final prefs = await _sharedPrefs;
       final cachePrefix = _getCachePrefix(artifactType);
       final cacheKey = '$cachePrefix$name';

       final artifactCache = StacCache(
         name: name,
         stacJson: stacJson,
         version: version,
         cachedAt: DateTime.now(),
       );

       return prefs.setString(cacheKey, artifactCache.toJsonString());
     } catch (e) {
+      Log.w('Failed to save artifact $name (${artifactType.name}): $e');
       return false;
     }
   }

   static Future<bool> removeArtifact(
     String artifactName,
     StacArtifactType artifactType,
   ) async {
     try {
       final prefs = await _sharedPrefs;
       final cachePrefix = _getCachePrefix(artifactType);
       final cacheKey = '$cachePrefix$artifactName';
       return prefs.remove(cacheKey);
     } catch (e) {
+      Log.w('Failed to remove artifact $artifactName (${artifactType.name}): $e');
       return false;
     }
   }

   static Future<bool> clearAllArtifacts(StacArtifactType artifactType) async {
     try {
       final prefs = await _sharedPrefs;
       final keys = prefs.getKeys();
       final cachePrefix = _getCachePrefix(artifactType);
       final cacheKeys = keys.where((key) => key.startsWith(cachePrefix));

       await Future.wait(cacheKeys.map(prefs.remove));

       return true;
     } catch (e) {
+      Log.w('Failed to clear all artifacts (${artifactType.name}): $e');
       return false;
     }
   }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1b077a1 and 87fc897.

📒 Files selected for processing (7)
  • packages/stac/lib/src/framework/stac_app.dart
  • packages/stac/lib/src/framework/stac_app_theme.dart
  • packages/stac/lib/src/models/models.dart
  • packages/stac/lib/src/models/stac_cache.dart
  • packages/stac/lib/src/models/stac_cache.g.dart
  • packages/stac/lib/src/services/stac_cache_service.dart
  • packages/stac/lib/src/services/stac_cloud.dart
✅ Files skipped from review due to trivial changes (1)
  • packages/stac/lib/src/models/stac_cache.g.dart
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/stac/lib/src/models/models.dart
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-12-19T20:20:55.561Z
Learnt from: akhil-ge0rge
Repo: StacDev/stac PR: 399
File: packages/stac/lib/src/parsers/theme/stac_tool_tip_theme_data_parser.dart:9-26
Timestamp: 2025-12-19T20:20:55.561Z
Learning: In Flutter, TooltipThemeData.height is deprecated since v3.30.0-0.1.pre. Replace uses with TooltipThemeData.constraints: BoxConstraints(minHeight: value) to achieve the same minimum height. Apply this pattern to review any Dart/Flutter code that uses TooltipThemeData.height, not just this file.

Applied to files:

  • packages/stac/lib/src/framework/stac_app.dart
  • packages/stac/lib/src/models/stac_cache.dart
  • packages/stac/lib/src/framework/stac_app_theme.dart
  • packages/stac/lib/src/services/stac_cloud.dart
  • packages/stac/lib/src/services/stac_cache_service.dart

Comment on lines +47 to +55
const StacAppTheme.network({
required BuildContext context,
required StacNetworkRequest request,
}) : _source = _ThemeSource.network,
name = null,
_context = context,
_request = request,
_jsonPayload = null,
_dslTheme = null;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Storing BuildContext in a field may cause issues.

The _context field stores a BuildContext that could become invalid if the creating widget is unmounted before resolve() is called. If the context is used after its widget is disposed, it may reference a deactivated element.

Consider passing the BuildContext at resolution time rather than construction time, or document that resolution must happen synchronously.

Alternative API approach

Option 1: Accept context in resolve() instead:

/// Resolves the theme based on the configured source.
Future<StacTheme?> resolve([BuildContext? context]) async {
  switch (_source) {
    case _ThemeSource.network:
      if (context == null) {
        throw ArgumentError('Context required for network theme resolution');
      }
      return fromNetwork(context: context, request: _request!);
    // ... other cases
  }
}

Option 2: Document the requirement clearly:

/// Creates a [StacAppTheme] wrapper for fetching a theme from network.
///
/// ⚠️ IMPORTANT: [resolve] must be called synchronously (e.g., in initState)
/// while the [context] is still valid. Do not store this wrapper for later use.
const StacAppTheme.network({
  required BuildContext context,
  required StacNetworkRequest request,
}) : // ...

- Updated the description to clarify the use of DSL and Cloud sources for theme management.
- Removed redundant sections on DSL themes and streamlined the explanation of theme loading methods.
- Adjusted the numbering of theme loading methods for consistency.
- Enhanced best practices and error handling sections for better guidance.
Copy link
Member

@divyanshub024 divyanshub024 left a comment

Choose a reason for hiding this comment

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

LGTM

@divyanshub024 divyanshub024 merged commit e209f56 into dev Jan 2, 2026
6 checks passed
@divyanshub024 divyanshub024 deleted the mn/themeing branch January 2, 2026 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants