Skip to content

Add Material pack option#874

Open
egorikftp wants to merge 3 commits intomainfrom
feature/material-pack
Open

Add Material pack option#874
egorikftp wants to merge 3 commits intomainfrom
feature/material-pack

Conversation

@egorikftp
Copy link
Member

@egorikftp egorikftp commented Feb 18, 2026

Screen.Recording.2026-02-18.at.21.49.46.mov

📝 Changelog

If this PR introduces user-facing changes, please update the relevant Unreleased section in changelogs:

@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

Walkthrough

This pull request refactors the IntelliJ IDE plugin's icon pack setup flows. It removes the useMaterialPack boolean property from persistent and in-memory settings, eliminates the ExpandedGroup composable and PackEditState wrapper, and introduces a dedicated Material Pack screen as a first-class navigation destination. The change includes reorganizing model types by moving existing pack types from ui.model to model packages, introducing a new DirectoryState for directory selection, and simplifying IconPackEditor to accept InputFieldState directly. String resource keys are updated from newpack to new.pack naming convention. New input handlers for Material and New Pack flows are added while removing the old NewPackInputHandler from ui.viewmodel.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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
Title check ✅ Passed The title 'Add Material pack option' directly reflects the main change in the PR, which adds a new Material pack feature and related screens, state management, and UI components throughout the codebase.
Description check ✅ Passed The PR description includes a video asset demonstrating the changes and marks all three changelog files (IntelliJ Plugin, CLI, Gradle Plugin) as updated, fulfilling the template requirements for user-facing changes.

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

✨ Finishing Touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/material-pack

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: 4

Caution

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

⚠️ Outside diff range comments (1)
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackEditor.kt (1)

76-90: ⚠️ Potential issue | 🟡 Minor

allowEditing not guarding the add-button in the empty-packs branch.

When nestedPacks.isEmpty(), AddPackButton is rendered unconditionally (lines 78–81), but when nestedPacks.isNotEmpty(), the equivalent button inside NestedPacks is gated behind allowEditing (line 135). The Material pack flow will typically start with an empty nested-pack list, so if it sets allowEditing = false, the "Add nested pack" button would still appear — a functional regression in that flow.

🐛 Proposed fix
-        if (inputFieldState.nestedPacks.isEmpty()) {
-            Spacer(8.dp)
-            AddPackButton(
-                modifier = Modifier.align(Alignment.Start),
-                onClick = onAddNestedPack,
-            )
-        } else {
+        if (inputFieldState.nestedPacks.isNotEmpty()) {
             NestedPacks(
                 nestedPacks = inputFieldState.nestedPacks,
                 allowEditing = inputFieldState.allowEditing,
                 onRemove = onRemoveNestedPack,
                 onValueChange = onValueChange,
                 onAddNestedPack = onAddNestedPack,
             )
+        } else if (inputFieldState.allowEditing) {
+            Spacer(8.dp)
+            AddPackButton(
+                modifier = Modifier.align(Alignment.Start),
+                onClick = onAddNestedPack,
+            )
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackEditor.kt`
around lines 76 - 90, The empty-branch renders AddPackButton unconditionally
which bypasses the allowEditing guard used inside NestedPacks; update the
empty-packs branch in IconPackEditor so AddPackButton is only shown when
inputFieldState.allowEditing is true (e.g., wrap the Spacer + AddPackButton
rendering in a check for inputFieldState.allowEditing or render a
disabled/hidden placeholder when false) so behavior matches the NestedPacks
path; reference the AddPackButton and inputFieldState.allowEditing in your
change.
🧹 Nitpick comments (7)
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/inputhandler/InputHandler.kt (1)

30-33: Pre-existing: nestedPacks.size.toString() can produce duplicate IDs.

Scenario: add (id="0"), add (id="1"), remove "0", add → id="1" again. Two packs share "1", and removeNestedPack (which uses filterNot { it.id == nestedPack.id }) would then silently delete both.

The onAddNestedPack lambda in the IconPackEditorPreview already uses Random.nextLong().toString() to avoid this. Consider aligning BasicInputHandler with the same approach or using a UUID.

♻️ Suggested fix
 override fun addNestedPack() {
     _state.updateState {
         copy(
             nestedPacks = nestedPacks + NestedPack(
-                id = nestedPacks.size.toString(),
+                id = java.util.UUID.randomUUID().toString(),
                 inputFieldState = InputState(),
             ),
         )
     }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/inputhandler/InputHandler.kt`
around lines 30 - 33, The nested pack ID generation in BasicInputHandler
currently uses nestedPacks.size.toString(), which can create duplicate IDs after
removals; change the creation of NestedPack in the onAddNestedPack flow to use a
unique ID generator (match IconPackEditorPreview) such as
Random.nextLong().toString() or UUID.randomUUID().toString() instead of
nestedPacks.size.toString() so each NestedPack.id is globally unique and
removeNestedPack (which filters by id) will only remove the intended pack.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/ui/MaterialPackCreation.kt (1)

51-51: Consider a dedicated string key for the Material Pack save action.

"iconpack.newpack.creation.continue" is semantically scoped to the NewPack flow. Reusing it works today since the button text is identical, but the namespacing will mislead if the two flows ever need different labels.

♻️ Suggested string key
-                Text(text = stringResource("iconpack.newpack.creation.continue"))
+                Text(text = stringResource("iconpack.materialpack.creation.save"))

Add a corresponding entry in the bundle:

iconpack.materialpack.creation.save=Save

(or whatever the final copy is)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/ui/MaterialPackCreation.kt`
at line 51, Replace the reused NewPack string key with a dedicated Material Pack
save key: add a new resource entry (e.g. "iconpack.materialpack.creation.save"
-> "Save" or final copy) to the strings bundle, then update the Text composable
call that currently uses stringResource("iconpack.newpack.creation.continue") to
use stringResource("iconpack.materialpack.creation.save") instead; locate the
usage in MaterialPackCreation (the Text/stringResource call) and ensure the new
key is added to the appropriate localization file.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt (1)

42-43: Consider using a buffered Channel for event delivery robustness

Channel<NewPackEvent>() defaults to RENDEZVOUS capacity (0), meaning send suspends until a receiver is active. While LaunchedEffect(Unit) in the screen starts collecting quickly, a brief window during recomposition or initial composition could leave the coroutine stuck on _events.send(...) until the collector resumes.

♻️ Proposed change
-    private val _events = Channel<NewPackEvent>()
+    private val _events = Channel<NewPackEvent>(Channel.BUFFERED)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt`
around lines 42 - 43, The Channel used for event delivery (_events of type
Channel<NewPackEvent> in NewPackViewModel) is rendezvous by default and can
suspend senders during brief gaps in collection; change its construction to a
buffered channel (for example using Channel(Channel.BUFFERED) or a small fixed
capacity like Channel(1)) so _events.send(...) won't suspend if the collector is
not immediately ready, and keep exposing events via events =
_events.receiveAsFlow().
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackDirectoryPicker.kt (1)

42-42: Minor style inconsistency: prefer rememberMutableState over remember { mutableStateOf(...) }

ChooseExistingPackFile.kt (line 48) uses the project's rememberMutableState helper for the same isDragging pattern. Using it here keeps the codebase consistent.

♻️ Proposed change
-    val isDragging by remember(dragAndDropHandler.isDragging) { mutableStateOf(dragAndDropHandler.isDragging) }
+    val isDragging by rememberMutableState(dragAndDropHandler.isDragging) { dragAndDropHandler.isDragging }

Also add to the imports:

+import io.github.composegears.valkyrie.sdk.compose.foundation.rememberMutableState
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackDirectoryPicker.kt`
at line 42, Replace the manual remember { mutableStateOf(...) } usage for
isDragging with the project's helper: use
rememberMutableState(dragAndDropHandler.isDragging) in IconPackDirectoryPicker
(replace the line defining val isDragging by ...), and add the corresponding
import for rememberMutableState to the file so it matches the pattern used in
ChooseExistingPackFile.kt.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/IconPackModeScreen.kt (1)

86-92: Use a more semantically appropriate icon for the Material Pack option

AllIconsKeys.FileTypes.SourceMap is designed for JavaScript source map files and doesn't communicate "Material Design icons" to users. However, reviewing the available IntelliJ platform icons in AllIconsKeys, there are no palette, layers, or design-themed alternatives. Consider using AllIconsKeys.General.Settings or AllIconsKeys.Actions.Properties as an interim solution, or explore if custom icons can be added to represent Material Design more intuitively.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/IconPackModeScreen.kt`
around lines 86 - 92, Replace the semantically incorrect icon
AllIconsKeys.FileTypes.SourceMap used in the InfoCard for the Material Pack
option (InfoCard with title stringResource("iconpack.mode.material.pack.title")
and onClick = onMaterialPack) with a more appropriate interim IntelliJ icon such
as AllIconsKeys.General.Settings or AllIconsKeys.Actions.Properties; update the
icon reference passed to the InfoCard's key parameter and ensure the change is
applied where InfoCard(...) for the Material Pack is declared so the UI better
communicates "Material Design" to users (consider adding a TODO to revisit and
supply a custom Material icon later).
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackScreen.kt (1)

43-53: Optional: Simplify event collection in LaunchedEffect.

flow.onEach { }.launchIn(this) inside a LaunchedEffect launches a redundant nested coroutine — the LaunchedEffect block itself becomes a fire-and-forget launcher. A direct collect is cleaner and avoids the extra coroutine layer:

♻️ Proposed simplification
 LaunchedEffect(Unit) {
-    viewModel.events
-        .onEach {
-            when (it) {
-                is MaterialPackEvent.FinishSetup -> {
-                    navController.replace(dest = IconPackConversionScreen)
-                }
-            }
-        }
-        .launchIn(this)
+    viewModel.events.collect {
+        when (it) {
+            is MaterialPackEvent.FinishSetup -> navController.replace(dest = IconPackConversionScreen)
+        }
+    }
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackScreen.kt`
around lines 43 - 53, The LaunchedEffect is using viewModel.events.onEach { ...
}.launchIn(this), which spawns a redundant nested coroutine; replace that
pattern by directly collecting the flow inside the LaunchedEffect: call
viewModel.events.collect { when (it) { is MaterialPackEvent.FinishSetup ->
navController.replace(dest = IconPackConversionScreen) } } so you remove
onEach(...).launchIn(this) and use collect within the existing LaunchedEffect to
simplify and avoid the extra coroutine layer.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackViewModel.kt (1)

39-40: Consider a buffered Channel for the events channel.

Channel() defaults to RENDEZVOUS (capacity = 0): send suspends until a receiver is ready. If saveIconPack() races to send FinishSetup before LaunchedEffect starts collecting (e.g., during recomposition), the sending coroutine in viewModelScope will hang indefinitely. ExistingPackViewModel uses MutableSharedFlow instead — consider aligning or using Channel(Channel.BUFFERED).

♻️ Proposed fix
-private val _events = Channel<MaterialPackEvent>()
+private val _events = Channel<MaterialPackEvent>(Channel.BUFFERED)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackViewModel.kt`
around lines 39 - 40, The events channel uses an unbuffered Channel (private val
_events = Channel<MaterialPackEvent>), which can suspend senders (e.g., in
saveIconPack() running in viewModelScope) if no collector (LaunchedEffect) is
ready; change this to a buffered channel or align with ExistingPackViewModel by
replacing the channel with Channel(Channel.BUFFERED) (or switch to a
MutableSharedFlow/SharedFlow) so sending FinishSetup (MaterialPackEvent) cannot
hang, and keep the public events as _events.receiveAsFlow() to preserve the API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tools/idea-plugin/CHANGELOG.md`:
- Line 8: Change the past-tense entry "Added separate \"Material pack\" option"
to imperative mood to match the rest of the changelog; replace that text with
"Add separate \"Material pack\" option" so the entry uses the same verb tense as
the "Add ..." entries.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/tooling/PreviewTheme.kt`:
- Around line 36-37: Move the side-effectful calls out of the composable body
and into a DisposableEffect: call GlobalPreviewState.isPreview = true and
DI.initWith(project) inside DisposableEffect tied to project (or Unit) and in
its onDispose set GlobalPreviewState.isPreview = false (and optionally perform
any DI cleanup if available); this ensures DI.initWith(project) runs under
Compose effect tracking and GlobalPreviewState.isPreview is reset on disposal.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/existingpack/ExistingPackViewModel.kt`:
- Around line 119-130: The call to IconPackWriter.savePack in
ExistingPackViewModel is relying on the default writeToFile=true, which creates
an implicit divergence from MaterialPackViewModel (which passes
writeToFile=false); update the IconPackWriter.savePack invocation inside
ExistingPackViewModel.viewModelScope.launch to explicitly pass writeToFile =
true (and do the same in NewPackViewModel where applicable) so the behavior is
explicit and consistent with MaterialPackViewModel's explicit writeToFile=false
usage.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt`:
- Line 40: Change the mutable property inputHandler to an immutable val since
it's only initialized once and never reassigned; locate the declaration "private
var inputHandler = BasicInputHandler(InputFieldState.Empty)" in NewPackViewModel
and replace var with val so you keep the same BasicInputHandler instance while
retaining existing method calls and property accesses on inputHandler.

---

Outside diff comments:
In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackEditor.kt`:
- Around line 76-90: The empty-branch renders AddPackButton unconditionally
which bypasses the allowEditing guard used inside NestedPacks; update the
empty-packs branch in IconPackEditor so AddPackButton is only shown when
inputFieldState.allowEditing is true (e.g., wrap the Spacer + AddPackButton
rendering in a check for inputFieldState.allowEditing or render a
disabled/hidden placeholder when false) so behavior matches the NestedPacks
path; reference the AddPackButton and inputFieldState.allowEditing in your
change.

---

Nitpick comments:
In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackDirectoryPicker.kt`:
- Line 42: Replace the manual remember { mutableStateOf(...) } usage for
isDragging with the project's helper: use
rememberMutableState(dragAndDropHandler.isDragging) in IconPackDirectoryPicker
(replace the line defining val isDragging by ...), and add the corresponding
import for rememberMutableState to the file so it matches the pattern used in
ChooseExistingPackFile.kt.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/inputhandler/InputHandler.kt`:
- Around line 30-33: The nested pack ID generation in BasicInputHandler
currently uses nestedPacks.size.toString(), which can create duplicate IDs after
removals; change the creation of NestedPack in the onAddNestedPack flow to use a
unique ID generator (match IconPackEditorPreview) such as
Random.nextLong().toString() or UUID.randomUUID().toString() instead of
nestedPacks.size.toString() so each NestedPack.id is globally unique and
removeNestedPack (which filters by id) will only remove the intended pack.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/IconPackModeScreen.kt`:
- Around line 86-92: Replace the semantically incorrect icon
AllIconsKeys.FileTypes.SourceMap used in the InfoCard for the Material Pack
option (InfoCard with title stringResource("iconpack.mode.material.pack.title")
and onClick = onMaterialPack) with a more appropriate interim IntelliJ icon such
as AllIconsKeys.General.Settings or AllIconsKeys.Actions.Properties; update the
icon reference passed to the InfoCard's key parameter and ensure the change is
applied where InfoCard(...) for the Material Pack is declared so the UI better
communicates "Material Design" to users (consider adding a TODO to revisit and
supply a custom Material icon later).

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackScreen.kt`:
- Around line 43-53: The LaunchedEffect is using viewModel.events.onEach { ...
}.launchIn(this), which spawns a redundant nested coroutine; replace that
pattern by directly collecting the flow inside the LaunchedEffect: call
viewModel.events.collect { when (it) { is MaterialPackEvent.FinishSetup ->
navController.replace(dest = IconPackConversionScreen) } } so you remove
onEach(...).launchIn(this) and use collect within the existing LaunchedEffect to
simplify and avoid the extra coroutine layer.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackViewModel.kt`:
- Around line 39-40: The events channel uses an unbuffered Channel (private val
_events = Channel<MaterialPackEvent>), which can suspend senders (e.g., in
saveIconPack() running in viewModelScope) if no collector (LaunchedEffect) is
ready; change this to a buffered channel or align with ExistingPackViewModel by
replacing the channel with Channel(Channel.BUFFERED) (or switch to a
MutableSharedFlow/SharedFlow) so sending FinishSetup (MaterialPackEvent) cannot
hang, and keep the public events as _events.receiveAsFlow() to preserve the API.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/ui/MaterialPackCreation.kt`:
- Line 51: Replace the reused NewPack string key with a dedicated Material Pack
save key: add a new resource entry (e.g. "iconpack.materialpack.creation.save"
-> "Save" or final copy) to the strings bundle, then update the Text composable
call that currently uses stringResource("iconpack.newpack.creation.continue") to
use stringResource("iconpack.materialpack.creation.save") instead; locate the
usage in MaterialPackCreation (the Text/stringResource call) and ensure the new
key is added to the appropriate localization file.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt`:
- Around line 42-43: The Channel used for event delivery (_events of type
Channel<NewPackEvent> in NewPackViewModel) is rendezvous by default and can
suspend senders during brief gaps in collection; change its construction to a
buffered channel (for example using Channel(Channel.BUFFERED) or a small fixed
capacity like Channel(1)) so _events.send(...) won't suspend if the collector is
not immediately ready, and keep exposing events via events =
_events.receiveAsFlow().

@egorikftp egorikftp force-pushed the feature/material-pack branch from fb82121 to f56af14 Compare February 18, 2026 19:44
@egorikftp egorikftp force-pushed the feature/material-pack branch from f56af14 to 88d872c Compare February 18, 2026 19:45
@egorikftp
Copy link
Member Author

@coderabbitai review all

@coderabbitai
Copy link

coderabbitai bot commented Feb 18, 2026

@egorikftp: I'll review all the changes in this PR.

✅ 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.

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: 2

Caution

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

⚠️ Outside diff range comments (1)
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackEditor.kt (1)

76-90: ⚠️ Potential issue | 🟡 Minor

Code inconsistency: allowEditing not checked when nestedPacks is empty.

When nestedPacks is empty (line 76), AddPackButton is rendered without checking inputFieldState.allowEditing. When nestedPacks is non-empty, allowEditing gates the button (line 135). While Material Pack always pre-populates nested packs (avoiding this scenario), the inconsistency leaves the code fragile. For consistency and defensive coding, apply the check in both branches.

Proposed fix
         if (inputFieldState.nestedPacks.isEmpty()) {
-            Spacer(8.dp)
-            AddPackButton(
-                modifier = Modifier.align(Alignment.Start),
-                onClick = onAddNestedPack,
-            )
+            if (inputFieldState.allowEditing) {
+                Spacer(8.dp)
+                AddPackButton(
+                    modifier = Modifier.align(Alignment.Start),
+                    onClick = onAddNestedPack,
+                )
+            }
         } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackEditor.kt`
around lines 76 - 90, The branch that renders AddPackButton when
inputFieldState.nestedPacks.isEmpty() should also respect
inputFieldState.allowEditing; change the empty-branch to only render
AddPackButton when inputFieldState.allowEditing is true (same gating used when
nestedPacks is non-empty) so AddPackButton is not shown or clickable when
editing is disallowed, keeping behavior consistent between the empty and
non-empty branches; update the conditional around AddPackButton (referencing
inputFieldState, allowEditing, AddPackButton, onAddNestedPack, and NestedPacks)
accordingly.
🧹 Nitpick comments (6)
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialInputHandler.kt (1)

26-32: Consider using semantic IDs for NestedPack instead of sequential integers.

The IDs "0""4" are unambiguous since the list is static and non-removable, but using the icon set name as the ID (matching the text value) is more self-documenting and easier to reason about if IDs surface in logs or error paths.

♻️ Suggested refactor
-                    NestedPack(id = "0", inputFieldState = InputState(text = "Filled", enabled = false)),
-                    NestedPack(id = "1", inputFieldState = InputState(text = "Outlined", enabled = false)),
-                    NestedPack(id = "2", inputFieldState = InputState(text = "Rounded", enabled = false)),
-                    NestedPack(id = "3", inputFieldState = InputState(text = "TwoTone", enabled = false)),
-                    NestedPack(id = "4", inputFieldState = InputState(text = "Sharp", enabled = false)),
+                    NestedPack(id = "filled", inputFieldState = InputState(text = "Filled", enabled = false)),
+                    NestedPack(id = "outlined", inputFieldState = InputState(text = "Outlined", enabled = false)),
+                    NestedPack(id = "rounded", inputFieldState = InputState(text = "Rounded", enabled = false)),
+                    NestedPack(id = "twoTone", inputFieldState = InputState(text = "TwoTone", enabled = false)),
+                    NestedPack(id = "sharp", inputFieldState = InputState(text = "Sharp", enabled = false)),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialInputHandler.kt`
around lines 26 - 32, Replace the numeric string IDs in the nestedPacks list
with semantic IDs that match the icon set names; update the NestedPack entries
inside nestedPacks (the list containing NestedPack(...) with inputFieldState =
InputState(...)) so each NestedPack.id uses the corresponding text value
("Filled", "Outlined", "Rounded", "TwoTone", "Sharp") instead of "0".."4" to
make IDs self-descriptive and match InputState.text.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackScreen.kt (1)

134-141: Preview duplicates the DirectoryState initialization literal.

The same DirectoryState(iconPackDestination = "path/to/import", predictedPackage = "com.example.iconpack", nextAvailable = true) appears in both the initial state (line 136) and the onBack handler (line 180). Consider extracting a preview constant to avoid drift between the two.

Also applies to: 179-185

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackScreen.kt`
around lines 134 - 141, The DirectoryState literal is duplicated in the initial
state and the onBack handler; extract it to a shared immutable preview constant
(e.g., PREVIEW_DIRECTORY_STATE or previewDirectoryState) and use that constant
when constructing ChooseDestinationDirectoryState in rememberMutableState and
inside the onBack handler; update references to
DirectoryState(iconPackDestination = "path/to/import", predictedPackage =
"com.example.iconpack", nextAvailable = true) so both NewPackModeState creation
and the onBack use the single constant (refer to rememberMutableState,
ChooseDestinationDirectoryState, DirectoryState, and the onBack handler).
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/existingpack/ExistingPackViewModel.kt (1)

49-50: MutableSharedFlow for events is inconsistent with sibling ViewModels and can silently drop events.

NewPackViewModel and MaterialPackViewModel were migrated to Channel + receiveAsFlow() in this PR, which suspends send() until the collector is ready — guaranteeing delivery. ExistingPackViewModel still uses MutableSharedFlow(replay=0), meaning emit() completes immediately even with no subscriber, potentially dropping navigation events (e.g., OnSettingsUpdated, PreviewIconPackObject) if the UI collector is transiently unsubscribed.

♻️ Suggested fix
-    private val _events = MutableSharedFlow<ExistingPackEvent>()
-    val events = _events.asSharedFlow()
+    private val _events = Channel<ExistingPackEvent>()
+    val events = _events.receiveAsFlow()

And replace all _events.emit(...) calls with _events.send(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/existingpack/ExistingPackViewModel.kt`
around lines 49 - 50, Replace the MutableSharedFlow-based event bus in
ExistingPackViewModel with a rendezvous Channel to match siblings: change the
backing _events from MutableSharedFlow<ExistingPackEvent>() to a
Channel<ExistingPackEvent>(Channel.RENDEZVOUS) (or simply Channel()), expose
events as _events.receiveAsFlow(), and update all places that call
_events.emit(...) to use _events.send(...) so sends suspend until a collector is
ready; refer to symbols ExistingPackViewModel, _events, events, and
ExistingPackEvent when making the edits.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/ui/MaterialPackCreation.kt (2)

44-46: No-op onAddNestedPack/onRemoveNestedPack are safe but should be guarded if the state could have empty nestedPacks.

When inputFieldState.nestedPacks.isEmpty(), IconPackEditor shows AddPackButton wired to onAddNestedPack = {} — clicking it silently does nothing. For the current Material pack state this is unreachable (Material icons always have nested packs), but it's a latent UX dead-end if the state ever has empty packs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/ui/MaterialPackCreation.kt`
around lines 44 - 46, The IconPackEditor is being passed no-op lambdas for
onAddNestedPack/onRemoveNestedPack which leads to a dead-end when
inputFieldState.nestedPacks.isEmpty(); update MaterialPackCreation so it
guards/handles empty nestedPacks by providing a real handler or conditional UI:
either supply a concrete onAddNestedPack implementation that inserts a default
nested pack into inputFieldState.nestedPacks (or calls the existing state
updater) and a matching onRemoveNestedPack, or wrap the IconPackEditor call with
a check on inputFieldState.nestedPacks.isEmpty() to show an alternative
Add-first UI/button that calls the pack-adding logic; refer to IconPackEditor,
AddPackButton, onAddNestedPack, onRemoveNestedPack and
inputFieldState.nestedPacks to locate where to add the guard/implementation.

56-56: Reusing "iconpack.newpack.creation.continue" string key in the Material pack flow — consider a dedicated key.

The button label is borrowed from the New Pack namespace. As long as the text is identical, this works — but it creates a hidden coupling: a future New Pack wording change would silently affect Material Pack UI.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/ui/MaterialPackCreation.kt`
at line 56, The MaterialPackCreation composable currently reuses the
"iconpack.newpack.creation.continue" string key which creates a hidden coupling;
replace that key with a dedicated one (e.g., "materialpack.creation.continue")
and update the strings resource file(s) accordingly, then change the Text(...)
call in MaterialPackCreation to use the new key so the Material pack flow has
its own independent localization entry.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackViewModel.kt (1)

83-101: updateDestinationPath and saveDestination are near-identical to NewPackViewModel — consider extracting shared logic.

Both ViewModels implement structurally identical functions (same PackageExtractor call, same DirectoryState construction, same inMemorySettings.update pattern). The only difference is NewPackViewModel.saveDestination also sets flatPackage = false. This duplication will need to be kept in sync as the flows evolve.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackViewModel.kt`
around lines 83 - 101, Extract the duplicated logic into a shared helper: create
a function (e.g., buildDirectoryState(path: Path): DirectoryState) that uses
PackageExtractor.getFrom(path.invariantSeparatorsPathString).orEmpty() and
path.absolutePathString(), then have updateDestinationPath use that to set
ChooseDestinationDirectoryState; likewise extract a
saveDestinationToSettings(directoryState: DirectoryState, setFlatPackage:
Boolean = false) that calls inMemorySettings.update { iconPackDestination =
directoryState.iconPackDestination; if (setFlatPackage) flatPackage = false },
and replace both updateDestinationPath and saveDestination to call these helpers
(ensure NewPackViewModel.saveDestination passes setFlatPackage = true to
preserve its difference); reference functions: updateDestinationPath,
saveDestination, NewPackViewModel.saveDestination, PackageExtractor,
DirectoryState, ChooseDestinationDirectoryState, inMemorySettings.update,
currentState.safeAs.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/button/OutlineIconButton.kt`:
- Around line 53-56: Replace the disabled icon tint selection that currently
reads the button border color and unsafe-casts it with a semantic content color:
in OutlineIconButton (the tint variable/when expression that references
style.colors.borderDisabled and safeAs<SolidColor>()), use
style.colors.contentDisabled directly (with a fallback to Color.Unspecified)
when enabled is false so the icon uses the proper disabled foreground color and
you avoid brittle Brush casting.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt`:
- Line 45: The ViewModel's initial _state is being set to a blank
ChooseDestinationDirectoryState(), which regresses restoration of persisted
destination data; change the initialization of _state
(MutableStateFlow<NewPackModeState>) to use the persisted settings by calling
inMemorySettings.current.toChooseDirectoryState() so the initial
NewPackModeState is populated with saved iconPackDestination, predictedPackage,
and nextAvailable values instead of empty defaults.

---

Outside diff comments:
In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackEditor.kt`:
- Around line 76-90: The branch that renders AddPackButton when
inputFieldState.nestedPacks.isEmpty() should also respect
inputFieldState.allowEditing; change the empty-branch to only render
AddPackButton when inputFieldState.allowEditing is true (same gating used when
nestedPacks is non-empty) so AddPackButton is not shown or clickable when
editing is disallowed, keeping behavior consistent between the empty and
non-empty branches; update the conditional around AddPackButton (referencing
inputFieldState, allowEditing, AddPackButton, onAddNestedPack, and NestedPacks)
accordingly.

---

Duplicate comments:
In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/tooling/PreviewTheme.kt`:
- Around line 36-37: Both assignments are side effects executed on every
recomposition: wrap GlobalPreviewState.isPreview = true and DI.initWith(project)
inside a DisposableEffect keyed to project (or Unit) within the composable so
they run once when entered and are cleaned up on disposal; set
GlobalPreviewState.isPreview = true in the effect and reset it to false in
DisposableEffect.onDispose, and call DI.initWith(project) in the effect and
perform any necessary DI cleanup in onDispose (refer to
GlobalPreviewState.isPreview and DI.initWith(project) to locate code).

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/existingpack/ExistingPackViewModel.kt`:
- Around line 120-125: The call in ExistingPackViewModel to
IconPackWriter.savePack inside the withContext(Dispatchers.IO) block relies on
the default writeToFile value (true); make this explicit by adding writeToFile =
false to the savePack call (matching MaterialPackViewModel and
NewPackViewModel), so update the IconPackWriter.savePack invocation in
ExistingPackViewModel to pass writeToFile = false.

---

Nitpick comments:
In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/existingpack/ExistingPackViewModel.kt`:
- Around line 49-50: Replace the MutableSharedFlow-based event bus in
ExistingPackViewModel with a rendezvous Channel to match siblings: change the
backing _events from MutableSharedFlow<ExistingPackEvent>() to a
Channel<ExistingPackEvent>(Channel.RENDEZVOUS) (or simply Channel()), expose
events as _events.receiveAsFlow(), and update all places that call
_events.emit(...) to use _events.send(...) so sends suspend until a collector is
ready; refer to symbols ExistingPackViewModel, _events, events, and
ExistingPackEvent when making the edits.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialInputHandler.kt`:
- Around line 26-32: Replace the numeric string IDs in the nestedPacks list with
semantic IDs that match the icon set names; update the NestedPack entries inside
nestedPacks (the list containing NestedPack(...) with inputFieldState =
InputState(...)) so each NestedPack.id uses the corresponding text value
("Filled", "Outlined", "Rounded", "TwoTone", "Sharp") instead of "0".."4" to
make IDs self-descriptive and match InputState.text.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackViewModel.kt`:
- Around line 83-101: Extract the duplicated logic into a shared helper: create
a function (e.g., buildDirectoryState(path: Path): DirectoryState) that uses
PackageExtractor.getFrom(path.invariantSeparatorsPathString).orEmpty() and
path.absolutePathString(), then have updateDestinationPath use that to set
ChooseDestinationDirectoryState; likewise extract a
saveDestinationToSettings(directoryState: DirectoryState, setFlatPackage:
Boolean = false) that calls inMemorySettings.update { iconPackDestination =
directoryState.iconPackDestination; if (setFlatPackage) flatPackage = false },
and replace both updateDestinationPath and saveDestination to call these helpers
(ensure NewPackViewModel.saveDestination passes setFlatPackage = true to
preserve its difference); reference functions: updateDestinationPath,
saveDestination, NewPackViewModel.saveDestination, PackageExtractor,
DirectoryState, ChooseDestinationDirectoryState, inMemorySettings.update,
currentState.safeAs.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/ui/MaterialPackCreation.kt`:
- Around line 44-46: The IconPackEditor is being passed no-op lambdas for
onAddNestedPack/onRemoveNestedPack which leads to a dead-end when
inputFieldState.nestedPacks.isEmpty(); update MaterialPackCreation so it
guards/handles empty nestedPacks by providing a real handler or conditional UI:
either supply a concrete onAddNestedPack implementation that inserts a default
nested pack into inputFieldState.nestedPacks (or calls the existing state
updater) and a matching onRemoveNestedPack, or wrap the IconPackEditor call with
a check on inputFieldState.nestedPacks.isEmpty() to show an alternative
Add-first UI/button that calls the pack-adding logic; refer to IconPackEditor,
AddPackButton, onAddNestedPack, onRemoveNestedPack and
inputFieldState.nestedPacks to locate where to add the guard/implementation.
- Line 56: The MaterialPackCreation composable currently reuses the
"iconpack.newpack.creation.continue" string key which creates a hidden coupling;
replace that key with a dedicated one (e.g., "materialpack.creation.continue")
and update the strings resource file(s) accordingly, then change the Text(...)
call in MaterialPackCreation to use the new key so the Material pack flow has
its own independent localization entry.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackScreen.kt`:
- Around line 134-141: The DirectoryState literal is duplicated in the initial
state and the onBack handler; extract it to a shared immutable preview constant
(e.g., PREVIEW_DIRECTORY_STATE or previewDirectoryState) and use that constant
when constructing ChooseDestinationDirectoryState in rememberMutableState and
inside the onBack handler; update references to
DirectoryState(iconPackDestination = "path/to/import", predictedPackage =
"com.example.iconpack", nextAvailable = true) so both NewPackModeState creation
and the onBack use the single constant (refer to rememberMutableState,
ChooseDestinationDirectoryState, DirectoryState, and the onBack handler).

Comment on lines +53 to +56
val tint = when {
enabled -> Color.Unspecified
else -> style.colors.borderDisabled.safeAs<SolidColor>()?.value ?: Color.Unspecified
}
Copy link

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:

#!/bin/bash
# Find the ButtonColors definition in Jewel to verify the fields
fd -e kt -e kts | xargs rg -l "class ButtonColors|interface ButtonColors" | head -5

Repository: ComposeGears/Valkyrie

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search for ButtonColors definition and its properties
rg "data class ButtonColors|class ButtonColors|interface ButtonColors" -A 20 --type kotlin

Repository: ComposeGears/Valkyrie

Length of output: 47


🏁 Script executed:

#!/bin/bash
# More targeted search for ButtonColors in jewel package
find . -path "*/jewel/*" -name "*.kt" | xargs rg -l "ButtonColors" | head -10

Repository: ComposeGears/Valkyrie

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Check the file's imports and look for ButtonColors source
head -30 tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/button/OutlineIconButton.kt

Repository: ComposeGears/Valkyrie

Length of output: 1378


🏁 Script executed:

#!/bin/bash
# Search for build.gradle or pom.xml to see if jewel is a dependency
find . -name "build.gradle*" -o -name "pom.xml" | xargs grep -l "jewel" 2>/dev/null | head -5

Repository: ComposeGears/Valkyrie

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Look for any ButtonColors usage in the codebase to understand its structure
rg "ButtonColors" -A 3 -B 3 --type kotlin | head -100

Repository: ComposeGears/Valkyrie

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Read the full OutlineIconButton.kt file
cat -n tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/button/OutlineIconButton.kt

Repository: ComposeGears/Valkyrie

Length of output: 3659


🏁 Script executed:

#!/bin/bash
# Search for contentDisabled usage in the entire codebase to see if it's used elsewhere
rg "contentDisabled" --type kotlin

Repository: ComposeGears/Valkyrie

Length of output: 47


🏁 Script executed:

#!/bin/bash
# Search for other button files that might use style.colors to understand the pattern
find . -path "*/jewel/button/*.kt" -type f -exec basename {} \; | sort | uniq

Repository: ComposeGears/Valkyrie

Length of output: 156


🏁 Script executed:

#!/bin/bash
# Look for other uses of borderDisabled in the codebase
rg "borderDisabled" --type kotlin -B 2 -A 2

Repository: ComposeGears/Valkyrie

Length of output: 752


🏁 Script executed:

#!/bin/bash
# Check how other button files handle disabled/tint logic
cat -n tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/button/TooltipIconButton.kt

Repository: ComposeGears/Valkyrie

Length of output: 2457


🏁 Script executed:

#!/bin/bash
# Search for all uses of style.colors in the codebase to understand the pattern
rg "style\.colors\." --type kotlin -B 2 -A 2

Repository: ComposeGears/Valkyrie

Length of output: 752


🏁 Script executed:

#!/bin/bash
# Search for how colors are accessed in Jewel-based components across the codebase
rg "\.colors\." --type kotlin | grep -E "(button|Button|style\.colors)" | head -20

Repository: ComposeGears/Valkyrie

Length of output: 261


🌐 Web query:

Jewel ButtonColors contentDisabled API documentation

💡 Result:

In JetBrains Jewel (Compose for Desktop), ButtonColors is part of the org.jetbrains.jewel:jewel-ui artifact, and contentDisabled is the color used for a button’s content (text/icon) when the button is disabled.

How to find the API docs:

  • Online Javadoc (latest): open the jewel-ui docs on javadoc.io and search for ButtonColorscontentDisabled. The latest published version shown there is 0.33.0-253.29795. [1]
  • Downloadable Javadoc JAR: Maven Central publishes a *-javadoc.jar for that same version (useful if you want to browse docs offline or attach them in IntelliJ). [2]
  • Source location: the old JetBrains/jewel repo was archived and development moved into JetBrains/intellij-community under platform/jewel. [3]

Sources: [1] [2] [3]


🌐 Web query:

Jewel UI button colors borderDisabled contentDisabled property

💡 Result:

In JetBrains Jewel UI, the ButtonColors API exposes:

  • borderDisabled: the border color used when the button is disabled (enabled = false). Disabled button styling typically has a dedicated border.disabled token/key in JetBrains UI theming systems. [1]
  • contentDisabled: the content (text/icon) color used when the button is disabled (enabled = false). Disabled button styling typically has a dedicated text.disabled token/key. [1]

You normally don’t set these as separate parameters on Button itself—rather, you provide a ButtonColors instance (via the button’s colors = ...) where borderDisabled / contentDisabled are overridden, while other states keep their defaults.

Source(s): JetBrains theme color keys reference showing distinct button.*.border.disabled and button.*.text.disabled keys for disabled button border/text colors. [1]


Use contentDisabled instead of borderDisabled for icon tinting.

borderDisabled is the button's border stroke color when disabled, not its content (icon/text) color. Jewel's ButtonColors exposes a dedicated contentDisabled property for disabled foreground colors — this is semantically correct and simpler than casting:

✂️ Proposed change
-        val tint = when {
-            enabled -> Color.Unspecified
-            else -> style.colors.borderDisabled.safeAs<SolidColor>()?.value ?: Color.Unspecified
-        }
+        val tint = if (enabled) Color.Unspecified else style.colors.contentDisabled

The current code's safeAs<SolidColor>()?.value cast and fallback also mean that if borderDisabled is ever a non-solid Brush, the disabled icon silently renders without tint — visually indistinguishable from the enabled state.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/button/OutlineIconButton.kt`
around lines 53 - 56, Replace the disabled icon tint selection that currently
reads the button border color and unsafe-casts it with a semantic content color:
in OutlineIconButton (the tint variable/when expression that references
style.colors.borderDisabled and safeAs<SolidColor>()), use
style.colors.contentDisabled directly (with a fallback to Color.Unspecified)
when enabled is false so the icon uses the proper disabled foreground color and
you avoid brittle Brush casting.

private val currentState: NewPackModeState
get() = state.value

private val _state = MutableStateFlow<NewPackModeState>(ChooseDestinationDirectoryState())
Copy link

@coderabbitai coderabbitai bot Feb 18, 2026

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:

# Search for ChooseDestinationDirectoryState and DirectoryState definitions
rg -n "data class ChooseDestinationDirectoryState|class ChooseDestinationDirectoryState|object ChooseDestinationDirectoryState" --type kotlin -A 10

Repository: ComposeGears/Valkyrie

Length of output: 3116


🏁 Script executed:

rg -n "data class DirectoryState|class DirectoryState" --type kotlin -A 8

Repository: ComposeGears/Valkyrie

Length of output: 829


🏁 Script executed:

# Also check if inMemorySettings is still being used in NewPackViewModel
rg -n "inMemorySettings" tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt

Repository: ComposeGears/Valkyrie

Length of output: 500


Regression confirmed: previously-saved destination no longer restored on ViewModel init.

The old initialization used inMemorySettings.current.toChooseDirectoryState() to pre-populate state from persisted settings. The new code initializes with ChooseDestinationDirectoryState(), which defaults to an empty DirectoryState (all fields blank). This causes previously saved iconPackDestination, predictedPackage, and nextAvailable values to be lost when the ViewModel is created, forcing returning users to re-enter their destination directory.

Since inMemorySettings is already injected and actively used elsewhere in the ViewModel (lines 35, 37, 61, 99, 121-122, 134), restore the initialization to use inMemorySettings.current.toChooseDirectoryState() to re-populate the state with saved values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt`
at line 45, The ViewModel's initial _state is being set to a blank
ChooseDestinationDirectoryState(), which regresses restoration of persisted
destination data; change the initialization of _state
(MutableStateFlow<NewPackModeState>) to use the persisted settings by calling
inMemorySettings.current.toChooseDirectoryState() so the initial
NewPackModeState is populated with saved iconPackDestination, predictedPackage,
and nextAvailable values instead of empty defaults.

Copy link
Member Author

Choose a reason for hiding this comment

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

it's new logic, i don't want old values

Copy link

Choose a reason for hiding this comment

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

@egorikftp, understood! Thank you for clarifying that this is intentional new logic. The fresh start with empty defaults for the new pack flow makes sense.


✏️ Learnings added
Learnt from: egorikftp
Repo: ComposeGears/Valkyrie PR: 874
File: tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt:45-45
Timestamp: 2026-02-18T20:05:17.471Z
Learning: In NewPackViewModel (tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt), the initialization of _state with ChooseDestinationDirectoryState() using empty defaults is intentional new logic. The ViewModel should NOT restore previously-saved destination values from inMemorySettings on init; users should start with a clean state each time.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: egorikftp
Repo: ComposeGears/Valkyrie PR: 866
File: tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/tools/svgxml/conversion/SvgXmlConversionScreen.kt:147-147
Timestamp: 2026-02-17T19:35:54.190Z
Learning: In SvgXmlConversionScreen.kt and similar editor screens using IntellijEditorTextField, the pattern `val code by rememberMutableState { state.xmlCode.value }` is intentional. The editor is initialized once with the ViewModel's value, then maintains its own internal Document state. User edits flow from editor → ViewModel via onChange callbacks (e.g., OnCodeChange action). This is the correct pattern for text editors where the user is editing content—the editor becomes the source of truth after initialization, not the ViewModel.

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.

🧹 Nitpick comments (5)
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/IconPackModeScreen.kt (1)

87-87: Optional: consider a more semantically appropriate icon for Material pack.

AllIconsKeys.FileTypes.SourceMap is the IDE's icon for JavaScript/TypeScript source-map files, which is thematically unrelated to Material Icons. A few alternatives that carry a "design/components" connotation: AllIconsKeys.Toolwindows.ToolWindowComponents, AllIconsKeys.Actions.Colors, or a custom icon. This has no functional impact.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/IconPackModeScreen.kt`
at line 87, The icon used for the Material pack is currently
AllIconsKeys.FileTypes.SourceMap which is semantically for source maps; update
the icon reference in IconPackModeScreen (the key =
AllIconsKeys.FileTypes.SourceMap usage) to a more appropriate icon such as
AllIconsKeys.Toolwindows.ToolWindowComponents or AllIconsKeys.Actions.Colors, or
swap in a custom Material icon resource; ensure you replace the referenced
symbol only (key assignment) so the rest of IconPackModeScreen remains
unchanged.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackDirectoryPicker.kt (1)

74-78: Refactor string keys to reflect generic usage, not NewPack-specific scope.

The component is used by both NewPackScreen and MaterialPackScreen, but all string references (lines 77, 90, 96, 107) use the iconpack.newpack namespace prefix, which semantically indicates they are NewPack-specific. While the actual text content ("Drag & Drop destination folder", "Destination path", etc.) is generic enough to work for both flows, the string key namespace should be refactored to iconpack.common to align with the component's placement in the common package.

Consider moving the string keys to a generic namespace or parameterizing them so the string scope matches the component's intent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackDirectoryPicker.kt`
around lines 74 - 78, The string keys used by IconPackDirectoryPicker (the
stringResource calls in this composable that currently use the
iconpack.newpack.* namespace) are NewPack-specific; refactor them to a generic
namespace (e.g., iconpack.common.choose.directory.dnd,
iconpack.common.destination.path, etc.) or accept string resource keys/strings
as parameters to IconPackDirectoryPicker so the component is truly common;
update the stringResource invocations in IconPackDirectoryPicker to use the new
iconpack.common keys (or the passed-in parameters) and add corresponding entries
to the resources.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackEditor.kt (1)

94-140: Clarify the semantic scope of allowEditing parameter.

The allowEditing flag (line 135) only gates the "Add nested pack" button, but delete buttons for individual packs remain controlled by per-pack inputFieldState.enabled flags. If this layered control is intentional, consider renaming allowEditing to allowAddingPacks for clarity, since it doesn't prevent deletion of existing packs.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackEditor.kt`
around lines 94 - 140, The parameter allowEditing on the NestedPacks composable
only controls showing the AddPackButton while deletion remains governed by each
nestedPack.inputFieldState.enabled; rename allowEditing to allowAddingPacks
(update the NestedPacks signature and all call sites) and update any related
KDoc/comments to reflect the narrower semantics, or alternatively change
behavior to gate deletes via allowAddingPacks && inputFieldState.enabled if you
intend a single editing flag; adjust usages of onRemove, AddPackButton, and any
docs to match the new name/behavior.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/ui/MaterialPackCreation.kt (1)

44-45: No-op lambdas for nested pack editing are intentional but could benefit from a brief comment.

Since the material pack flow uses pre-defined, non-editable nested packs (allowEditing = false), the empty lambdas are correct. A brief inline comment would clarify intent for future readers.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/ui/MaterialPackCreation.kt`
around lines 44 - 45, Add a short inline comment beside the empty lambdas
onAddNestedPack and onRemoveNestedPack in MaterialPackCreation.kt explaining
they are intentionally no-ops because nested packs are pre-defined and editing
is disabled (allowEditing = false); reference the onAddNestedPack and
onRemoveNestedPack parameters to locate the exact spot and keep the comment
concise so future readers understand this is intentional, not an omission.
tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackViewModel.kt (1)

41-42: Channel pattern is consistent codebase-wide; buffering not essential but could be considered.

Channel<MaterialPackEvent>() uses RENDEZVOUS capacity (default), matching the pattern across NewPackViewModel, SvgXmlViewModel, and other ViewModels. All send events from active coroutine contexts (user-triggered actions within viewModelScope.launch blocks), so the UI collector is ready when send() is called. Buffering with Channel(capacity = 1) is optional—the RENDEZVOUS pattern is intentional for one-shot event delivery and works reliably in this context.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackViewModel.kt`
around lines 41 - 42, The Channel in MaterialPackViewModel using the default
RENDEZVOUS capacity is intentional and consistent with other view models; update
the code by adding a brief comment near the _events/ events declarations
(referencing _events: Channel<MaterialPackEvent>(), events =
_events.receiveAsFlow(), and the MaterialPackViewModel) stating that RENDEZVOUS
is chosen intentionally for one-shot event delivery (matching NewPackViewModel,
SvgXmlViewModel) and that buffering is optional because sends occur from
viewModelScope coroutines where the UI collector is expected to be ready.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/button/OutlineIconButton.kt`:
- Around line 53-56: The disabled icon tint logic in OutlineIconButton.kt
currently reads style.colors.borderDisabled and casts to SolidColor, which is
wrong and brittle; change the tint computation to use
style.colors.contentDisabled (no cast to SolidColor and no fallback to
Color.Unspecified) so the icon uses the button's disabled foreground color;
after updating the tint expression, remove any now-unused SolidColor and safeAs
imports.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/jewel/tooling/PreviewTheme.kt`:
- Around line 36-37: Move the side-effectful calls out of the composable body
and into a DisposableEffect keyed appropriately: set
GlobalPreviewState.isPreview = true inside a DisposableEffect and restore it to
false in onDispose, and call DI.initWith(project) once inside the same (or a
separate) DisposableEffect so it runs only on composition entry and can be
cleaned up on dispose; update the PreviewTheme composable to wrap these
assignments in DisposableEffect blocks referencing the project (or Unit if
project is stable) and perform any necessary DI shutdown/cleanup in onDispose.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackViewModel.kt`:
- Around line 109-125: saveIconPack in MaterialPackViewModel is correct to call
IconPackWriter.savePack(writeToFile = false) and set
inMemorySettings.flatPackage = true; ensure this explicit behavior is preserved
here and make the other view models symmetric by making their intent explicit:
in NewPackViewModel and ExistingPackViewModel call inMemorySettings.update {
flatPackage = false } before invoking IconPackWriter.savePack and pass
writeToFile = true explicitly to savePack, so the difference between material
packs and regular packs is clear and not implicit.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/newpack/NewPackViewModel.kt`:
- Around line 45-46: The initial _state is created with a blank
ChooseDestinationDirectoryState() which ignores persisted destination; update
the NewPackViewModel initialization so _state =
MutableStateFlow<NewPackModeState>(...) is seeded from inMemorySettings.current
(use its iconPackDestination and any derived predictedPackage/nextAvailable)
instead of empty defaults, e.g. construct a ChooseDestinationDirectoryState
populated from inMemorySettings.current values so returning users see their
saved output folder and derived fields when the ViewModel is created.

---

Nitpick comments:
In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackDirectoryPicker.kt`:
- Around line 74-78: The string keys used by IconPackDirectoryPicker (the
stringResource calls in this composable that currently use the
iconpack.newpack.* namespace) are NewPack-specific; refactor them to a generic
namespace (e.g., iconpack.common.choose.directory.dnd,
iconpack.common.destination.path, etc.) or accept string resource keys/strings
as parameters to IconPackDirectoryPicker so the component is truly common;
update the stringResource invocations in IconPackDirectoryPicker to use the new
iconpack.common keys (or the passed-in parameters) and add corresponding entries
to the resources.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/common/IconPackEditor.kt`:
- Around line 94-140: The parameter allowEditing on the NestedPacks composable
only controls showing the AddPackButton while deletion remains governed by each
nestedPack.inputFieldState.enabled; rename allowEditing to allowAddingPacks
(update the NestedPacks signature and all call sites) and update any related
KDoc/comments to reflect the narrower semantics, or alternatively change
behavior to gate deletes via allowAddingPacks && inputFieldState.enabled if you
intend a single editing flag; adjust usages of onRemove, AddPackButton, and any
docs to match the new name/behavior.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/IconPackModeScreen.kt`:
- Line 87: The icon used for the Material pack is currently
AllIconsKeys.FileTypes.SourceMap which is semantically for source maps; update
the icon reference in IconPackModeScreen (the key =
AllIconsKeys.FileTypes.SourceMap usage) to a more appropriate icon such as
AllIconsKeys.Toolwindows.ToolWindowComponents or AllIconsKeys.Actions.Colors, or
swap in a custom Material icon resource; ensure you replace the referenced
symbol only (key assignment) so the rest of IconPackModeScreen remains
unchanged.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/MaterialPackViewModel.kt`:
- Around line 41-42: The Channel in MaterialPackViewModel using the default
RENDEZVOUS capacity is intentional and consistent with other view models; update
the code by adding a brief comment near the _events/ events declarations
(referencing _events: Channel<MaterialPackEvent>(), events =
_events.receiveAsFlow(), and the MaterialPackViewModel) stating that RENDEZVOUS
is chosen intentionally for one-shot event delivery (matching NewPackViewModel,
SvgXmlViewModel) and that buffering is optional because sends occur from
viewModelScope coroutines where the UI collector is expected to be ready.

In
`@tools/idea-plugin/src/main/kotlin/io/github/composegears/valkyrie/ui/screen/mode/iconpack/material/ui/MaterialPackCreation.kt`:
- Around line 44-45: Add a short inline comment beside the empty lambdas
onAddNestedPack and onRemoveNestedPack in MaterialPackCreation.kt explaining
they are intentionally no-ops because nested packs are pre-defined and editing
is disabled (allowEditing = false); reference the onAddNestedPack and
onRemoveNestedPack parameters to locate the exact spot and keep the comment
concise so future readers understand this is intentional, not an omission.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Comments