Skip to content

Bugfix/#344 disallow unknown destination#354

Merged
vkatz merged 6 commits intomainfrom
bugfix/#344-disallow-unknown-destination
Mar 3, 2026
Merged

Bugfix/#344 disallow unknown destination#354
vkatz merged 6 commits intomainfrom
bugfix/#344-disallow-unknown-destination

Conversation

@vkatz
Copy link
Contributor

@vkatz vkatz commented Mar 2, 2026

Summary by CodeRabbit

Release Notes

New Features

  • Introduced a new destination loading mechanism for flexible navigation resolution strategies.
  • Added graph composition support via the + operator for merging navigation graphs.
  • Added modifier parameter to preview functionality for enhanced customization.
  • Introduced stability annotation for compose integration.
  • Added new API annotation for flagging unsafe features.

Chores

  • Updated library version to 2.3.0.

@vkatz vkatz linked an issue Mar 2, 2026 that may be closed by this pull request
…unknown-destination

# Conflicts:
#	tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/ComposableNavigation.kt
@vkatz vkatz added bugfix Something isn't working enhancement New feature or request labels Mar 2, 2026
@vkatz
Copy link
Contributor Author

vkatz commented Mar 2, 2026

@CodeRabbit review

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link

coderabbitai bot commented Mar 2, 2026

Walkthrough

This PR introduces a new DestinationLoader abstraction to replace function-based destination resolution, adds a TiamatUnsafeApi annotation, brings key-based destination identification, and refactors navigation APIs throughout to use the loader pattern. Also adds graph merging capabilities and a Modifier parameter to TiamatPreview.

Changes

Cohort / File(s) Summary
Version Management
gradle/tiamat.toml
Bumps tiamat version from 2.2.0 to 2.3.0.
Sample Updates
sample/shared/src/commonMain/kotlin/composegears/tiamat/sample/App.kt
Minor formatting adjustment to trailing newlines around AppPreview.
New Unsafe API Annotation
tiamat/src/commonMain/kotlin/com/composegears/tiamat/TiamatUnsafeApi.kt, tiamat/api/jvm/tiamat.api, tiamat/api/tiamat.klib.api
Introduces TiamatUnsafeApi annotation with RequiresOptIn(WARNING) to mark potentially unsafe or unstable APIs requiring explicit opt-in.
Destination Loading Abstraction
tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/DestinationLoader.kt, tiamat/api/jvm/tiamat.api, tiamat/api/tiamat.klib.api
New sealed interface DestinationLoader with three implementations: DoNotLoad (always returns null), FromArray (loads from predefined destinations array), and ByKey (delegates to custom function). Provides factory methods via Companion: from() and byKey().
Navigation API Refactoring
tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/ComposableNavigation.kt, tiamat/api/jvm/tiamat.api, tiamat/api/tiamat.klib.api
Replaces destinationResolver function parameter with DestinationLoader across Navigation and NavigationScene overloads. Updates internal loading logic from resolver to loader pattern with entry.isLoaded and entry.load(destinationLoader) checks. Adds TiamatUnsafeApi opt-in where appropriate.
Preview Composable Update
tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/ComposablePreview.kt, tiamat/api/jvm/tiamat.api, tiamat/api/tiamat.klib.api
Adds Modifier parameter to TiamatPreview function signature. Switches to DestinationLoader.DoNotLoad for preview loading and applies the modifier to Navigation container.
NavEntry Loader Integration
tiamat/src/commonMain/kotlin/com/composegears/tiamat/navigation/NavEntry.kt
Adds internal load(destinationLoader: DestinationLoader) function. Renames isResolved to isLoaded. Replaces NavDestination.Unresolved with NavDestination.NotLoaded. Updates ensureDetachedAndAttach to use strict require guard.
NavDestination Key Property
tiamat/src/commonMain/kotlin/com/composegears/tiamat/navigation/NavDestination.kt, tiamat/api/jvm/tiamat.api, tiamat/api/tiamat.klib.api
Adds public key property (initialized to name) to NavDestination. Removes internal Unresolved class while keeping NotLoaded.
NavController Loader Refactoring
tiamat/src/commonMain/kotlin/com/composegears/tiamat/navigation/NavController.kt
Renames internal resolveNavDestinations(destinationResolver) to loadNavDestinations(destinationLoader). Updates route resolution to create NotLoaded entries instead of Unresolved. Updates state restoration to use isNullOrEmpty check.
NavController Composition Updates
tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/ComposableNavController.kt
Adds explicit else branch to shouldClear logic with default false condition, improving code clarity and control flow explicitness.
Navigation Tests
tiamat/src/commonTest/kotlin/com/composegears/tiamat/navigation/NavControllerTests.kt, tiamat/src/commonTest/kotlin/com/composegears/tiamat/navigation/NavEntryTests.kt
Updates tests to use DestinationLoader pattern. Replaces Unresolved with NotLoaded and isResolved checks with isLoaded. Creates loaders via DestinationLoader.from(). Removes old resolveDestination extension.
Documentation Updates
tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/TransitionController.kt
Expands KDoc for TransitionController describing active state semantics, update/cancel/finish behavior, error conditions, and default value requirements. No runtime behavior changes.
Graph Merging Capability
tiamat-destinations/tiamat-destinations/src/commonMain/kotlin/com/composegears/tiamat/destinations/TiamatGraph.kt
Adds @Stable annotation to TiamatGraph interface. Adds public operator fun plus(other: TiamatGraph) for graph merging. Provides default implementation for destinations() method returning error array when empty.

Possibly related PRs

Suggested reviewers

  • egorikftp
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Bugfix/#344 disallow unknown destination' directly addresses the main objective of preventing unregistered/unknown navigation destinations from being opened, which aligns with the significant architectural changes to implement destination loader and validation mechanisms across the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/#344-disallow-unknown-destination

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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

@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

Caution

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

⚠️ Outside diff range comments (1)
tiamat-destinations/tiamat-destinations/src/commonMain/kotlin/com/composegears/tiamat/destinations/TiamatGraph.kt (1)

31-39: ⚠️ Potential issue | 🟠 Major

Operator plus should not mutate the left operand.

Current behavior mutates this for merged graphs, creating inconsistency with the documented contract ("returns a new merged graph") and unexpected side effects in chained operations like a + b + c.

♻️ Proposed change
     public operator fun plus(other: TiamatGraph): TiamatGraph {
-        if (this is TiamatMergedGraph) {
-            this.allDestinations.addAll(other.destinations())
-            return this
-        } else {
-            val mergedGraph = TiamatMergedGraph()
-            mergedGraph.allDestinations.addAll(this.destinations())
-            mergedGraph.allDestinations.addAll(other.destinations())
-            return mergedGraph
-        }
+        val mergedGraph = TiamatMergedGraph()
+        mergedGraph.allDestinations.addAll(this.destinations())
+        mergedGraph.allDestinations.addAll(other.destinations())
+        return mergedGraph
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tiamat-destinations/tiamat-destinations/src/commonMain/kotlin/com/composegears/tiamat/destinations/TiamatGraph.kt`
around lines 31 - 39, The operator plus implementation currently mutates the
left operand when it is a TiamatMergedGraph; change it so it always returns a
new TiamatMergedGraph without mutating this. In the TiamatGraph.operator fun
plus(other: TiamatGraph) implementation, create a fresh TiamatMergedGraph
instance, addAll(this.destinations()) and addAll(other.destinations()) into that
new instance (instead of calling this.allDestinations.addAll), and return the
new instance; reference the TiamatMergedGraph class, the plus operator on
TiamatGraph, the allDestinations collection, and the destinations() method to
locate and update the code.
🧹 Nitpick comments (2)
tiamat-destinations/tiamat-destinations/src/commonMain/kotlin/com/composegears/tiamat/destinations/TiamatGraph.kt (1)

22-22: Prefer an abstract destinations() contract over runtime error().

This change weakens API safety by turning a compile-time requirement into a runtime crash path.

♻️ Proposed change
-    public fun destinations(): Array<NavDestination<*>> = error("Graph is empty")
+    public fun destinations(): Array<NavDestination<*>>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tiamat-destinations/tiamat-destinations/src/commonMain/kotlin/com/composegears/tiamat/destinations/TiamatGraph.kt`
at line 22, The destinations() function currently returns error("Graph is
empty"), which introduces a runtime crash; change the API to require
implementers to provide destinations at compile time by making destinations() an
abstract function (e.g., declare TiamatGraph as an abstract class or convert it
to an interface) with signature fun destinations(): Array<NavDestination<*>> so
concrete graph implementations must override it; update any classes
extending/implementing TiamatGraph to implement destinations() accordingly and
remove the runtime error fallback.
tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/DestinationLoader.kt (1)

62-67: Consider using a map for O(1) lookup in FromArray.

The current implementation uses firstOrNull which is O(n). For applications with many destinations, this could be optimized with a map-based lookup.

♻️ Optional optimization using Map
 public class FromArray(
     private val destinations: Array<NavDestination<*>>
 ) : DestinationLoader {
+    private val destinationMap = destinations.associateBy { it.key }
+    
     override fun load(key: String): NavDestination<*>? =
-        destinations.firstOrNull { it.key == key }
+        destinationMap[key]
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/DestinationLoader.kt`
around lines 62 - 67, The FromArray class uses destinations.firstOrNull { it.key
== key } (load) which is O(n); change it to build an index map (e.g., val
destinationByKey = destinations.associateBy { it.key }) once in the class
(constructor or init) and have load(key: String) return destinationByKey[key];
update the FromArray class to hold the Map alongside the existing destinations
and use it for O(1) lookups while preserving the same load signature and return
type.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/ComposablePreview.kt`:
- Around line 17-18: The KDoc for the ComposablePreview function references a
non-existent parameter "@param destinationLoader"; update the KDoc to match the
actual function signature in ComposablePreview.kt by removing the "@param
destinationLoader" line (or replace it with the correct parameter name if the
function should accept a loader), ensuring the remaining param tags (e.g.,
"@param modifier") match the symbols in the ComposablePreview function
declaration.

---

Outside diff comments:
In
`@tiamat-destinations/tiamat-destinations/src/commonMain/kotlin/com/composegears/tiamat/destinations/TiamatGraph.kt`:
- Around line 31-39: The operator plus implementation currently mutates the left
operand when it is a TiamatMergedGraph; change it so it always returns a new
TiamatMergedGraph without mutating this. In the TiamatGraph.operator fun
plus(other: TiamatGraph) implementation, create a fresh TiamatMergedGraph
instance, addAll(this.destinations()) and addAll(other.destinations()) into that
new instance (instead of calling this.allDestinations.addAll), and return the
new instance; reference the TiamatMergedGraph class, the plus operator on
TiamatGraph, the allDestinations collection, and the destinations() method to
locate and update the code.

---

Nitpick comments:
In
`@tiamat-destinations/tiamat-destinations/src/commonMain/kotlin/com/composegears/tiamat/destinations/TiamatGraph.kt`:
- Line 22: The destinations() function currently returns error("Graph is
empty"), which introduces a runtime crash; change the API to require
implementers to provide destinations at compile time by making destinations() an
abstract function (e.g., declare TiamatGraph as an abstract class or convert it
to an interface) with signature fun destinations(): Array<NavDestination<*>> so
concrete graph implementations must override it; update any classes
extending/implementing TiamatGraph to implement destinations() accordingly and
remove the runtime error fallback.

In
`@tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/DestinationLoader.kt`:
- Around line 62-67: The FromArray class uses destinations.firstOrNull { it.key
== key } (load) which is O(n); change it to build an index map (e.g., val
destinationByKey = destinations.associateBy { it.key }) once in the class
(constructor or init) and have load(key: String) return destinationByKey[key];
update the FromArray class to hold the Map alongside the existing destinations
and use it for O(1) lookups while preserving the same load signature and return
type.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8f9af3 and 884a278.

📒 Files selected for processing (16)
  • gradle/tiamat.toml
  • sample/shared/src/commonMain/kotlin/composegears/tiamat/sample/App.kt
  • tiamat-destinations/tiamat-destinations/src/commonMain/kotlin/com/composegears/tiamat/destinations/TiamatGraph.kt
  • tiamat/api/jvm/tiamat.api
  • tiamat/api/tiamat.klib.api
  • tiamat/src/commonMain/kotlin/com/composegears/tiamat/TiamatUnsafeApi.kt
  • tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/ComposableNavController.kt
  • tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/ComposableNavigation.kt
  • tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/ComposablePreview.kt
  • tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/DestinationLoader.kt
  • tiamat/src/commonMain/kotlin/com/composegears/tiamat/compose/TransitionController.kt
  • tiamat/src/commonMain/kotlin/com/composegears/tiamat/navigation/NavController.kt
  • tiamat/src/commonMain/kotlin/com/composegears/tiamat/navigation/NavDestination.kt
  • tiamat/src/commonMain/kotlin/com/composegears/tiamat/navigation/NavEntry.kt
  • tiamat/src/commonTest/kotlin/com/composegears/tiamat/navigation/NavControllerTests.kt
  • tiamat/src/commonTest/kotlin/com/composegears/tiamat/navigation/NavEntryTests.kt

@vkatz vkatz requested a review from egorikftp March 2, 2026 16:17
@vkatz vkatz merged commit 9ce4c8f into main Mar 3, 2026
7 checks passed
@vkatz vkatz deleted the bugfix/#344-disallow-unknown-destination branch March 3, 2026 10:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bugfix Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Disallow usage of unresolvable destinations in navController

2 participants