diff --git a/README.md b/README.md
index bde691ed..b0422933 100644
--- a/README.md
+++ b/README.md
@@ -175,6 +175,8 @@ That document is generated from each target’s
Dagger2-friendly `api`/`impl` + composition roots: **F-011** (landed).
Jetpack Compose: per-target `compose` flag + `composeWidget` — **F-013**
([`docs/COMPOSE.md`](docs/COMPOSE.md)).
+Sample multi-feature gold standard: **F-014**
+([`docs/SAMPLE-APP.md`](docs/SAMPLE-APP.md)).
Icons made by Freepik
from www.flaticon.com
diff --git a/TICKETS.md b/TICKETS.md
index d56a165c..0a8075c1 100644
--- a/TICKETS.md
+++ b/TICKETS.md
@@ -21,7 +21,7 @@ Update this file when picking or finishing work. Cron workers must pick the **hi
| F-011 | done | Tighten validation for `api` / `impl` (Dagger2-friendly boundaries) | app/binary/androidLibrary no longer EmptyValidator; impl still no→impl; docs matrix updated |
| F-012 | done | External deps catalog UX + tooling polish | `library()` + pure generators + GAV validation + unit tests + `docs/DEPS-CATALOG.md` |
| F-013 | done | Compose support for Android library/ui targets | GH #96; `compose` flags + `composeWidget`; `docs/COMPOSE.md` |
-| F-014 | todo | Sample app: gold-standard multi-feature structure | Home/characters already present; modernize |
+| F-014 | done | Sample app: gold-standard multi-feature structure | packageName + source-root alignment; `docs/SAMPLE-APP.md`; home/characters pattern |
| F-015 | todo | Android project tutorial (getting started) | GH #53 |
| F-016 | todo | Plugin publish path (Portal user + target publish config) | GH #132, #133 |
| F-017 | todo | Configuration-time performance pass | GH #106, #42 |
diff --git a/application/common/extensions/android-util/src/main/java/com/stepango/blockme/common/extensions/android/util/NavigationExtensions.kt b/application/common/extensions/android-util/src/main/java/tools/forma/sample/common/extensions/android/util/NavigationExtensions.kt
similarity index 100%
rename from application/common/extensions/android-util/src/main/java/com/stepango/blockme/common/extensions/android/util/NavigationExtensions.kt
rename to application/common/extensions/android-util/src/main/java/tools/forma/sample/common/extensions/android/util/NavigationExtensions.kt
diff --git a/application/common/extensions/util/src/main/java/com/stepango/blockme/common/extensions/util/ByteArrayExtensions.kt b/application/common/extensions/util/src/main/java/tools/forma/sample/common/extensions/util/ByteArrayExtensions.kt
similarity index 100%
rename from application/common/extensions/util/src/main/java/com/stepango/blockme/common/extensions/util/ByteArrayExtensions.kt
rename to application/common/extensions/util/src/main/java/tools/forma/sample/common/extensions/util/ByteArrayExtensions.kt
diff --git a/application/common/extensions/util/src/main/java/com/stepango/blockme/common/extensions/util/StringExtensions.kt b/application/common/extensions/util/src/main/java/tools/forma/sample/common/extensions/util/StringExtensions.kt
similarity index 100%
rename from application/common/extensions/util/src/main/java/com/stepango/blockme/common/extensions/util/StringExtensions.kt
rename to application/common/extensions/util/src/main/java/tools/forma/sample/common/extensions/util/StringExtensions.kt
diff --git a/application/common/util-native/src/main/jni/Secure.cpp b/application/common/util-native/src/main/jni/Secure.cpp
index bccc4f5e..18170e8e 100644
--- a/application/common/util-native/src/main/jni/Secure.cpp
+++ b/application/common/util-native/src/main/jni/Secure.cpp
@@ -5,7 +5,7 @@
#include
#include "Log.h"
-static const char* kClassPathName = "com/stepango/blockme/root/library/AppStartup";
+static const char* kClassPathName = "tools/forma/sample/root/library/AppStartup";
static const std::vector allowedPackages = { // NOLINT(cert-err58-cpp)
"tools.forma.sample.app"
};
diff --git a/application/core/mvvm/library/src/main/java/com/stepango/blockme/core/mvvm/library/lifecycle/SingleLiveData.kt b/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/SingleLiveData.kt
similarity index 100%
rename from application/core/mvvm/library/src/main/java/com/stepango/blockme/core/mvvm/library/lifecycle/SingleLiveData.kt
rename to application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/SingleLiveData.kt
diff --git a/application/core/mvvm/library/src/main/java/com/stepango/blockme/core/mvvm/library/lifecycle/ViewModelFactory.kt b/application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/ViewModelFactory.kt
similarity index 100%
rename from application/core/mvvm/library/src/main/java/com/stepango/blockme/core/mvvm/library/lifecycle/ViewModelFactory.kt
rename to application/core/mvvm/library/src/main/java/tools/forma/sample/core/mvvm/library/lifecycle/ViewModelFactory.kt
diff --git a/application/feature/characters/core/api/build.gradle.kts b/application/feature/characters/core/api/build.gradle.kts
index e3d01d3f..904c19d1 100644
--- a/application/feature/characters/core/api/build.gradle.kts
+++ b/application/feature/characters/core/api/build.gradle.kts
@@ -1,5 +1,5 @@
api(
- packageName = "tools.forma.sample.common.extensions.util",
+ packageName = "tools.forma.sample.feature.characters.core.api",
owner = Teams.core,
dependencies = deps(
squareup.retrofit
diff --git a/application/feature/characters/core/api/src/main/java/com/stepango/blockme/feature/characters/core/api/data/service/MarvelService.kt b/application/feature/characters/core/api/src/main/java/tools/forma/sample/feature/characters/core/api/data/service/MarvelService.kt
similarity index 100%
rename from application/feature/characters/core/api/src/main/java/com/stepango/blockme/feature/characters/core/api/data/service/MarvelService.kt
rename to application/feature/characters/core/api/src/main/java/tools/forma/sample/feature/characters/core/api/data/service/MarvelService.kt
diff --git a/application/feature/characters/core/impl/src/main/java/com/stepango/blockme/feature/characters/core/impl/data/mapper/CharacterMapper.kt b/application/feature/characters/core/impl/src/main/java/tools/forma/sample/feature/characters/core/impl/data/mapper/CharacterMapper.kt
similarity index 100%
rename from application/feature/characters/core/impl/src/main/java/com/stepango/blockme/feature/characters/core/impl/data/mapper/CharacterMapper.kt
rename to application/feature/characters/core/impl/src/main/java/tools/forma/sample/feature/characters/core/impl/data/mapper/CharacterMapper.kt
diff --git a/application/feature/characters/core/impl/src/main/java/com/stepango/blockme/feature/characters/core/impl/domain/repository/ServiceMarvelRepository.kt b/application/feature/characters/core/impl/src/main/java/tools/forma/sample/feature/characters/core/impl/domain/repository/ServiceMarvelRepository.kt
similarity index 100%
rename from application/feature/characters/core/impl/src/main/java/com/stepango/blockme/feature/characters/core/impl/domain/repository/ServiceMarvelRepository.kt
rename to application/feature/characters/core/impl/src/main/java/tools/forma/sample/feature/characters/core/impl/domain/repository/ServiceMarvelRepository.kt
diff --git a/application/feature/characters/core/impl/src/test/java/com/stepango/blockme/feature/characters/core/impl/domain/repository/ServiceMarvelRepositoryTest.kt b/application/feature/characters/core/impl/src/test/java/tools/forma/sample/feature/characters/core/impl/domain/repository/ServiceMarvelRepositoryTest.kt
similarity index 100%
rename from application/feature/characters/core/impl/src/test/java/com/stepango/blockme/feature/characters/core/impl/domain/repository/ServiceMarvelRepositoryTest.kt
rename to application/feature/characters/core/impl/src/test/java/tools/forma/sample/feature/characters/core/impl/domain/repository/ServiceMarvelRepositoryTest.kt
diff --git a/application/feature/characters/detail/api/build.gradle.kts b/application/feature/characters/detail/api/build.gradle.kts
index e8383bfa..10f9694b 100644
--- a/application/feature/characters/detail/api/build.gradle.kts
+++ b/application/feature/characters/detail/api/build.gradle.kts
@@ -1,5 +1,5 @@
api(
- packageName = "tools.forma.sample.character.detail.api",
+ packageName = "tools.forma.sample.feature.characters.detail.api",
owner = Teams.core,
dependencies = deps(
target(":feature:characters:core:api")
diff --git a/application/feature/characters/detail/impl/src/main/java/tools/forma/sample/feature/characters/detail/impl/ui/CharacterDetailFragment.kt b/application/feature/characters/detail/impl/src/main/java/tools/forma/sample/feature/characters/detail/impl/ui/CharacterDetailFragment.kt
index b5c63204..b0a6abf7 100644
--- a/application/feature/characters/detail/impl/src/main/java/tools/forma/sample/feature/characters/detail/impl/ui/CharacterDetailFragment.kt
+++ b/application/feature/characters/detail/impl/src/main/java/tools/forma/sample/feature/characters/detail/impl/ui/CharacterDetailFragment.kt
@@ -91,13 +91,13 @@ class CharacterDetailFragment : BaseViewBindingFragment(
viewBinding.addFavoriteButton.isVisible = viewState.isAddToFavorite()
when (viewState) {
is CharacterDetailViewState.Loading ->
- progressDialog.show(tools.forma.sample.feature.character.detail.res.R.string.character_detail_dialog_loading_text)
+ progressDialog.show(tools.forma.sample.feature.characters.detail.res.R.string.character_detail_dialog_loading_text)
is CharacterDetailViewState.Error ->
- progressDialog.dismissWithErrorMessage(tools.forma.sample.feature.character.detail.res.R.string.character_detail_dialog_error_text)
+ progressDialog.dismissWithErrorMessage(tools.forma.sample.feature.characters.detail.res.R.string.character_detail_dialog_error_text)
is CharacterDetailViewState.AddedToFavorite ->
Snackbar.make(
requireView(),
- tools.forma.sample.feature.character.detail.res.R.string.character_detail_added_to_favorite_message,
+ tools.forma.sample.feature.characters.detail.res.R.string.character_detail_added_to_favorite_message,
Snackbar.LENGTH_LONG
).show()
is CharacterDetailViewState.Dismiss ->
diff --git a/application/feature/characters/detail/res/build.gradle.kts b/application/feature/characters/detail/res/build.gradle.kts
index 45963af0..9191acc6 100644
--- a/application/feature/characters/detail/res/build.gradle.kts
+++ b/application/feature/characters/detail/res/build.gradle.kts
@@ -1,5 +1,5 @@
androidRes(
- packageName = "tools.forma.sample.feature.character.detail.res",
+ packageName = "tools.forma.sample.feature.characters.detail.res",
dependencies = deps(
google.material,
androidx.appcompat,
diff --git a/application/feature/characters/favorite/api/build.gradle.kts b/application/feature/characters/favorite/api/build.gradle.kts
index a037e134..0d1f1fb8 100644
--- a/application/feature/characters/favorite/api/build.gradle.kts
+++ b/application/feature/characters/favorite/api/build.gradle.kts
@@ -1,5 +1,5 @@
api(
- packageName = "tools.forma.sample.character.favorite.api",
+ packageName = "tools.forma.sample.feature.characters.favorite.api",
owner = Teams.core,
dependencies = deps(
target(":feature:characters:core:api")
diff --git a/application/feature/characters/favorite/impl/src/main/java/tools/forma/sample/feature/characters/favorite/impl/ui/CharacterFavoriteFragment.kt b/application/feature/characters/favorite/impl/src/main/java/tools/forma/sample/feature/characters/favorite/impl/ui/CharacterFavoriteFragment.kt
index 504436f9..48912349 100644
--- a/application/feature/characters/favorite/impl/src/main/java/tools/forma/sample/feature/characters/favorite/impl/ui/CharacterFavoriteFragment.kt
+++ b/application/feature/characters/favorite/impl/src/main/java/tools/forma/sample/feature/characters/favorite/impl/ui/CharacterFavoriteFragment.kt
@@ -33,10 +33,10 @@ import tools.forma.sample.feature.characters.favorite.impl.R
import tools.forma.sample.feature.characters.favorite.impl.di.DaggerCharacterFavoriteComponent
import tools.forma.sample.feature.characters.favorite.impl.ui.adapter.CharacterFavoriteAdapter
import tools.forma.sample.feature.characters.favorite.impl.ui.adapter.CharacterFavoriteTouchHelper
-import tools.forma.sample.feature.favorite.viewbinding.databinding.FragmentCharacterFavoriteListBinding
+import tools.forma.sample.feature.characters.favorite.viewbinding.databinding.FragmentCharacterFavoriteListBinding
class CharacterFavoriteFragment : BaseViewBindingFragment(
- layoutId = tools.forma.sample.feature.favorite.viewbinding.R.layout.fragment_character_favorite_list
+ layoutId = tools.forma.sample.feature.characters.favorite.viewbinding.R.layout.fragment_character_favorite_list
) {
private val viewModel: ICharacterFavoriteViewModel by viewModels()
@@ -64,7 +64,7 @@ class CharacterFavoriteFragment : BaseViewBindingFragment(
viewBinding.includeList.charactersFavoriteList.apply {
addItemDecoration(
RecyclerViewItemDecoration(
- resources.getDimensionPixelSize(tools.forma.sample.feature.character.favorite.res.R.dimen.characters_favorite_list_item_padding)
+ resources.getDimensionPixelSize(tools.forma.sample.feature.characters.favorite.res.R.dimen.characters_favorite_list_item_padding)
)
)
adapter = viewAdapter
diff --git a/application/feature/characters/favorite/impl/src/main/java/tools/forma/sample/feature/characters/favorite/impl/ui/adapter/holders/CharacterFavoriteViewHolder.kt b/application/feature/characters/favorite/impl/src/main/java/tools/forma/sample/feature/characters/favorite/impl/ui/adapter/holders/CharacterFavoriteViewHolder.kt
index 31c468b0..2b3921db 100644
--- a/application/feature/characters/favorite/impl/src/main/java/tools/forma/sample/feature/characters/favorite/impl/ui/adapter/holders/CharacterFavoriteViewHolder.kt
+++ b/application/feature/characters/favorite/impl/src/main/java/tools/forma/sample/feature/characters/favorite/impl/ui/adapter/holders/CharacterFavoriteViewHolder.kt
@@ -20,7 +20,7 @@ import android.view.LayoutInflater
import tools.forma.sample.common.extensions.android.util.loadImage
import tools.forma.sample.core.mvvm.library.ui.BaseViewHolder
import tools.forma.sample.feature.characters.core.api.domain.model.ICharacter
-import tools.forma.sample.feature.favorite.viewbinding.databinding.ListItemCharacterFavoriteBinding
+import tools.forma.sample.feature.characters.favorite.viewbinding.databinding.ListItemCharacterFavoriteBinding
class CharacterFavoriteViewHolder(
inflater: LayoutInflater
diff --git a/application/feature/characters/favorite/res/build.gradle.kts b/application/feature/characters/favorite/res/build.gradle.kts
index 17d0262c..98fe64fe 100644
--- a/application/feature/characters/favorite/res/build.gradle.kts
+++ b/application/feature/characters/favorite/res/build.gradle.kts
@@ -1,5 +1,5 @@
androidRes(
- packageName = "tools.forma.sample.feature.character.favorite.res",
+ packageName = "tools.forma.sample.feature.characters.favorite.res",
dependencies = deps(
google.material,
androidx.appcompat,
diff --git a/application/feature/characters/favorite/viewbinding/build.gradle.kts b/application/feature/characters/favorite/viewbinding/build.gradle.kts
index af2b3acc..d8657f6a 100644
--- a/application/feature/characters/favorite/viewbinding/build.gradle.kts
+++ b/application/feature/characters/favorite/viewbinding/build.gradle.kts
@@ -1,5 +1,5 @@
viewBinding(
- packageName = "tools.forma.sample.feature.favorite.viewbinding",
+ packageName = "tools.forma.sample.feature.characters.favorite.viewbinding",
dependencies = deps(
google.material,
androidx.navigation,
diff --git a/application/feature/characters/list/api/build.gradle.kts b/application/feature/characters/list/api/build.gradle.kts
index f9e99b3b..3ab959ff 100644
--- a/application/feature/characters/list/api/build.gradle.kts
+++ b/application/feature/characters/list/api/build.gradle.kts
@@ -1,4 +1,4 @@
api(
- packageName = "tools.forma.sample.character.list.api",
+ packageName = "tools.forma.sample.feature.characters.list.api",
owner = Teams.core,
)
\ No newline at end of file
diff --git a/application/feature/characters/list/api/src/main/empty.kt b/application/feature/characters/list/api/src/main/empty.kt
deleted file mode 100644
index e69de29b..00000000
diff --git a/application/feature/characters/list/api/src/main/java/tools/forma/sample/feature/characters/list/api/FeatureCharactersListApi.kt b/application/feature/characters/list/api/src/main/java/tools/forma/sample/feature/characters/list/api/FeatureCharactersListApi.kt
new file mode 100644
index 00000000..790cb80c
--- /dev/null
+++ b/application/feature/characters/list/api/src/main/java/tools/forma/sample/feature/characters/list/api/FeatureCharactersListApi.kt
@@ -0,0 +1,7 @@
+package tools.forma.sample.feature.characters.list.api
+
+/**
+ * Public contracts for the characters-list feature.
+ * UI wiring lives in `impl` / `viewbinding`; keep this module dependency-light.
+ */
+object FeatureCharactersListApi
diff --git a/application/feature/characters/list/impl/build.gradle.kts b/application/feature/characters/list/impl/build.gradle.kts
index 263fc093..5fa18173 100644
--- a/application/feature/characters/list/impl/build.gradle.kts
+++ b/application/feature/characters/list/impl/build.gradle.kts
@@ -16,9 +16,9 @@ impl(
viewbinding.viewpropertydelegate,
) +
deps(libs.jakewhartonTimber) +
- deps(projects.featureCharactersCoreApi) +
deps(
- target(projects.featureCharactersListApi),
+ target(":feature:characters:core:api"),
+ target(":feature:characters:list:api"),
target(":feature:characters:list:viewbinding"),
target(":feature:characters:list:res"),
target(":core:di:library"),
diff --git a/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListFragment.kt b/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListFragment.kt
index 7d94519e..aafaafb8 100644
--- a/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListFragment.kt
+++ b/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListFragment.kt
@@ -32,8 +32,8 @@ import tools.forma.sample.feature.characters.core.api.domain.model.ICharacter
import tools.forma.sample.feature.characters.list.viewbinding.databinding.FragmentCharactersListBinding
import tools.forma.sample.feature.characters.list.impl.R
import tools.forma.sample.feature.characters.list.impl.di.DaggerCharactersListComponent
-import tools.forma.sample.feature.characters.list.impl.domain.model.ICharactersListViewEvent
-import tools.forma.sample.feature.characters.list.impl.domain.model.ICharactersListViewState
+import tools.forma.sample.feature.characters.list.viewbinding.domain.model.ICharactersListViewEvent
+import tools.forma.sample.feature.characters.list.viewbinding.domain.model.ICharactersListViewState
import tools.forma.sample.feature.characters.list.impl.ui.adapter.CharactersListAdapter
import tools.forma.sample.feature.characters.list.impl.ui.adapter.CharactersListAdapterState
@@ -70,7 +70,7 @@ class CharactersListFragment : BaseViewBindingFragment(
viewBinding.includeList.charactersList.apply {
addItemDecoration(
RecyclerViewItemDecoration(
- resources.getDimensionPixelSize(tools.forma.sample.feature.character.list.res.R.dimen.characters_list_item_padding)
+ resources.getDimensionPixelSize(tools.forma.sample.feature.characters.list.res.R.dimen.characters_list_item_padding)
)
)
adapter = viewAdapter
diff --git a/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewEvent.kt b/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewEvent.kt
index ec366dad..cc02e5a5 100644
--- a/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewEvent.kt
+++ b/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewEvent.kt
@@ -16,7 +16,7 @@
package tools.forma.sample.feature.characters.list.impl.ui
-import tools.forma.sample.feature.characters.list.impl.domain.model.ICharactersListViewEvent
+import tools.forma.sample.feature.characters.list.viewbinding.domain.model.ICharactersListViewEvent
sealed class CharactersListViewEvent : ICharactersListViewEvent {
diff --git a/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewModel.kt b/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewModel.kt
index 43e8ebb9..949bd9c7 100644
--- a/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewModel.kt
+++ b/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewModel.kt
@@ -26,9 +26,9 @@ import tools.forma.sample.core.network.library.NetworkState
import tools.forma.sample.feature.characters.list.impl.data.datasource.CharactersPageDataSourceFactory
import tools.forma.sample.feature.characters.list.impl.data.datasource.PAGE_MAX_ELEMENTS
import tools.forma.sample.feature.characters.core.api.domain.model.ICharacter
-import tools.forma.sample.feature.characters.list.impl.domain.model.ICharactersListViewEvent
-import tools.forma.sample.feature.characters.list.impl.domain.model.ICharactersListViewModel
-import tools.forma.sample.feature.characters.list.impl.domain.model.ICharactersListViewState
+import tools.forma.sample.feature.characters.list.viewbinding.domain.model.ICharactersListViewEvent
+import tools.forma.sample.feature.characters.list.viewbinding.domain.model.ICharactersListViewModel
+import tools.forma.sample.feature.characters.list.viewbinding.domain.model.ICharactersListViewState
import javax.inject.Inject
class CharactersListViewModel @Inject constructor(
diff --git a/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewState.kt b/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewState.kt
index b2b2bc04..d1b6c2b0 100644
--- a/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewState.kt
+++ b/application/feature/characters/list/impl/src/main/java/tools/forma/sample/feature/characters/list/impl/ui/CharactersListViewState.kt
@@ -17,7 +17,7 @@
package tools.forma.sample.feature.characters.list.impl.ui
import tools.forma.sample.core.mvvm.library.ui.BaseViewState
-import tools.forma.sample.feature.characters.list.impl.domain.model.ICharactersListViewState
+import tools.forma.sample.feature.characters.list.viewbinding.domain.model.ICharactersListViewState
sealed class CharactersListViewState : BaseViewState, ICharactersListViewState {
diff --git a/application/feature/characters/list/res/build.gradle.kts b/application/feature/characters/list/res/build.gradle.kts
index 5cf7c39e..5285fd61 100644
--- a/application/feature/characters/list/res/build.gradle.kts
+++ b/application/feature/characters/list/res/build.gradle.kts
@@ -1,5 +1,5 @@
androidRes(
- packageName = "tools.forma.sample.feature.character.list.res",
+ packageName = "tools.forma.sample.feature.characters.list.res",
dependencies = deps(
google.material,
androidx.appcompat,
diff --git a/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/impl/domain/model/ICharactersListViewEvent.kt b/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/impl/domain/model/ICharactersListViewEvent.kt
deleted file mode 100644
index be9a2ee0..00000000
--- a/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/impl/domain/model/ICharactersListViewEvent.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-package tools.forma.sample.feature.characters.list.impl.domain.model
-
-interface ICharactersListViewEvent
\ No newline at end of file
diff --git a/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/viewbinding/domain/model/ICharactersListViewEvent.kt b/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/viewbinding/domain/model/ICharactersListViewEvent.kt
new file mode 100644
index 00000000..8257658c
--- /dev/null
+++ b/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/viewbinding/domain/model/ICharactersListViewEvent.kt
@@ -0,0 +1,3 @@
+package tools.forma.sample.feature.characters.list.viewbinding.domain.model
+
+interface ICharactersListViewEvent
\ No newline at end of file
diff --git a/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/impl/domain/model/ICharactersListViewModel.kt b/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/viewbinding/domain/model/ICharactersListViewModel.kt
similarity index 92%
rename from application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/impl/domain/model/ICharactersListViewModel.kt
rename to application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/viewbinding/domain/model/ICharactersListViewModel.kt
index 59225d7c..7a6aabf7 100644
--- a/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/impl/domain/model/ICharactersListViewModel.kt
+++ b/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/viewbinding/domain/model/ICharactersListViewModel.kt
@@ -1,4 +1,4 @@
-package tools.forma.sample.feature.characters.list.impl.domain.model
+package tools.forma.sample.feature.characters.list.viewbinding.domain.model
import androidx.lifecycle.LiveData
import androidx.paging.PagedList
diff --git a/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/impl/domain/model/ICharactersListViewState.kt b/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/viewbinding/domain/model/ICharactersListViewState.kt
similarity index 94%
rename from application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/impl/domain/model/ICharactersListViewState.kt
rename to application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/viewbinding/domain/model/ICharactersListViewState.kt
index 08830817..675e3679 100644
--- a/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/impl/domain/model/ICharactersListViewState.kt
+++ b/application/feature/characters/list/viewbinding/src/main/java/tools/forma/sample/feature/characters/list/viewbinding/domain/model/ICharactersListViewState.kt
@@ -1,4 +1,4 @@
-package tools.forma.sample.feature.characters.list.impl.domain.model
+package tools.forma.sample.feature.characters.list.viewbinding.domain.model
interface ICharactersListViewState {
/**
diff --git a/application/feature/home/api/build.gradle.kts b/application/feature/home/api/build.gradle.kts
index 2277c543..34f08309 100644
--- a/application/feature/home/api/build.gradle.kts
+++ b/application/feature/home/api/build.gradle.kts
@@ -1,4 +1,4 @@
api(
- packageName = "tools.forma.sample.home.api",
+ packageName = "tools.forma.sample.feature.home.api",
owner = Teams.core
)
\ No newline at end of file
diff --git a/application/feature/home/api/src/main/java/tools/forma/sample/feature/home/api/FeatureHomeApi.kt b/application/feature/home/api/src/main/java/tools/forma/sample/feature/home/api/FeatureHomeApi.kt
new file mode 100644
index 00000000..962590ff
--- /dev/null
+++ b/application/feature/home/api/src/main/java/tools/forma/sample/feature/home/api/FeatureHomeApi.kt
@@ -0,0 +1,6 @@
+package tools.forma.sample.feature.home.api
+
+/**
+ * Public contracts for the home shell feature (bottom nav host).
+ */
+object FeatureHomeApi
diff --git a/application/feature/home/api/src/main/java/tools/forma/sample/feature/home/api/empty.kt b/application/feature/home/api/src/main/java/tools/forma/sample/feature/home/api/empty.kt
deleted file mode 100644
index 1200f2ce..00000000
--- a/application/feature/home/api/src/main/java/tools/forma/sample/feature/home/api/empty.kt
+++ /dev/null
@@ -1,2 +0,0 @@
-package tools.forma.sample.feature.home.api
-
diff --git a/application/feature/home/impl/src/main/java/tools/forma/sample/feature/home/impl/di/HomeModule.kt b/application/feature/home/impl/src/main/java/tools/forma/sample/feature/home/impl/di/HomeModule.kt
index 980700cd..f02582ae 100644
--- a/application/feature/home/impl/src/main/java/tools/forma/sample/feature/home/impl/di/HomeModule.kt
+++ b/application/feature/home/impl/src/main/java/tools/forma/sample/feature/home/impl/di/HomeModule.kt
@@ -3,7 +3,7 @@ package tools.forma.sample.feature.home.impl.di
import androidx.lifecycle.ViewModel
import tools.forma.sample.core.di.library.scopes.FeatureScope
import tools.forma.sample.core.mvvm.library.di.ViewModelKey
-import tools.forma.sample.feature.home.impl.ui.HomeViewModel
+import tools.forma.sample.feature.home.viewbinding.ui.HomeViewModel
import dagger.Binds
import dagger.Module
import dagger.multibindings.IntoMap
diff --git a/application/feature/home/impl/src/main/java/tools/forma/sample/feature/home/impl/ui/HomeFragment.kt b/application/feature/home/impl/src/main/java/tools/forma/sample/feature/home/impl/ui/HomeFragment.kt
index 665a4a9e..90546696 100644
--- a/application/feature/home/impl/src/main/java/tools/forma/sample/feature/home/impl/ui/HomeFragment.kt
+++ b/application/feature/home/impl/src/main/java/tools/forma/sample/feature/home/impl/ui/HomeFragment.kt
@@ -32,8 +32,9 @@ import tools.forma.sample.core.theme.android.util.ThemeUtils
import tools.forma.sample.core.theme.android.util.di.ThemeComponentProvider
import tools.forma.sample.feature.home.impl.R
import tools.forma.sample.feature.home.impl.di.DaggerHomeComponent
-import tools.forma.sample.feature.home.impl.ui.menu.ToggleThemeCheckBox
import tools.forma.sample.feature.home.viewbinding.databinding.FragmentHomeBinding
+import tools.forma.sample.feature.home.viewbinding.ui.HomeViewModel
+import tools.forma.sample.widget.toggle.ToggleThemeCheckBox
import javax.inject.Inject
private const val DELAY_TO_APPLY_THEME = 1000L
diff --git a/application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/impl/ui/HomeViewModel.kt b/application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/viewbinding/ui/HomeViewModel.kt
similarity index 96%
rename from application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/impl/ui/HomeViewModel.kt
rename to application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/viewbinding/ui/HomeViewModel.kt
index 6951a5dc..287469ad 100644
--- a/application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/impl/ui/HomeViewModel.kt
+++ b/application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/viewbinding/ui/HomeViewModel.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package tools.forma.sample.feature.home.impl.ui
+package tools.forma.sample.feature.home.viewbinding.ui
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
diff --git a/application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/impl/ui/HomeViewState.kt b/application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/viewbinding/ui/HomeViewState.kt
similarity index 94%
rename from application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/impl/ui/HomeViewState.kt
rename to application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/viewbinding/ui/HomeViewState.kt
index f0fcc54e..7ffa35cc 100644
--- a/application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/impl/ui/HomeViewState.kt
+++ b/application/feature/home/viewbinding/src/main/java/tools/forma/sample/feature/home/viewbinding/ui/HomeViewState.kt
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package tools.forma.sample.feature.home.impl.ui
+package tools.forma.sample.feature.home.viewbinding.ui
import tools.forma.sample.core.mvvm.library.ui.BaseViewState
diff --git a/application/root-app/src/main/java/tools/forma/sample/root/library/SampleMainActivity.kt b/application/root-app/src/main/java/tools/forma/sample/root/library/SampleMainActivity.kt
index d898ac1a..371b8631 100644
--- a/application/root-app/src/main/java/tools/forma/sample/root/library/SampleMainActivity.kt
+++ b/application/root-app/src/main/java/tools/forma/sample/root/library/SampleMainActivity.kt
@@ -37,6 +37,6 @@ class SampleMainActivity : AppCompatActivity() {
*/
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- setContentView(tools.forma.sample.rootres.R.layout.activity_main)
+ setContentView(tools.forma.sample.root.res.R.layout.activity_main)
}
}
diff --git a/application/root-res/build.gradle.kts b/application/root-res/build.gradle.kts
index 62477070..cccf702b 100644
--- a/application/root-res/build.gradle.kts
+++ b/application/root-res/build.gradle.kts
@@ -1,5 +1,5 @@
androidRes(
- packageName = "tools.forma.sample.rootres",
+ packageName = "tools.forma.sample.root.res",
dependencies = deps(
androidx.constraintlayout,
androidx.navigation
diff --git a/application/toggle-widget/src/main/java/tools/forma/sample/feature/home/impl/ui/menu/ToggleThemeCheckBox.kt b/application/toggle-widget/src/main/java/tools/forma/sample/widget/toggle/ToggleThemeCheckBox.kt
similarity index 89%
rename from application/toggle-widget/src/main/java/tools/forma/sample/feature/home/impl/ui/menu/ToggleThemeCheckBox.kt
rename to application/toggle-widget/src/main/java/tools/forma/sample/widget/toggle/ToggleThemeCheckBox.kt
index 6db00978..9e7fe377 100644
--- a/application/toggle-widget/src/main/java/tools/forma/sample/feature/home/impl/ui/menu/ToggleThemeCheckBox.kt
+++ b/application/toggle-widget/src/main/java/tools/forma/sample/widget/toggle/ToggleThemeCheckBox.kt
@@ -1,4 +1,4 @@
-package tools.forma.sample.feature.home.impl.ui.menu
+package tools.forma.sample.widget.toggle
import android.content.Context
import android.util.AttributeSet
diff --git a/application/toggle-widget/src/main/res/menu/toolbar_menu.xml b/application/toggle-widget/src/main/res/menu/toolbar_menu.xml
index fffbce5b..d18331c8 100644
--- a/application/toggle-widget/src/main/res/menu/toolbar_menu.xml
+++ b/application/toggle-widget/src/main/res/menu/toolbar_menu.xml
@@ -10,7 +10,7 @@
diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md
index 9d1222d4..5bd39e10 100644
--- a/docs/ARCHITECTURE.md
+++ b/docs/ARCHITECTURE.md
@@ -197,7 +197,8 @@ Sample enables `arbitraryBuildScriptNames = true`.
## 3. Sample `application/` structure
-~35 Forma targets, multi-feature Rick-and-Morty style demo.
+~35 Forma targets, multi-feature Marvel demo. **User-facing gold-standard
+guide:** [`SAMPLE-APP.md`](SAMPLE-APP.md) (F-014).
```
application/
@@ -206,14 +207,15 @@ application/
├── root-res/ androidRes
├── toggle-widget/ widget
├── core/
-│ ├── di/library library
-│ ├── mvvm/library library
-│ ├── navigation/library library
-│ ├── network/library library
+│ ├── di/library androidLibrary
+│ ├── mvvm/library androidLibrary
+│ ├── navigation/library androidLibrary
+│ ├── network/library library (JVM)
│ └── theme/{android-util,res}
├── common/
│ ├── util util
│ ├── extensions/{util,android-util}
+│ ├── greeting/compose-widget composeWidget
│ ├── placeholder/res
│ ├── progressbar/{res,viewbinding}
│ └── recyclerview/widget
@@ -232,6 +234,7 @@ Wiring pattern:
- Feature **impl** = Android library + Dagger + navigation + viewbinding/res/widget
- **binary** depends on root-app + all feature api/impl + shared core (explicit
graph; not only transitive)
+- `packageName` aligned to path under `tools.forma.sample…` (F-014)
Root configuration (`application/build.gradle.kts`):
diff --git a/docs/DEPENDENCY-MATRIX.md b/docs/DEPENDENCY-MATRIX.md
index 5c3dc7d9..962b8036 100644
--- a/docs/DEPENDENCY-MATRIX.md
+++ b/docs/DEPENDENCY-MATRIX.md
@@ -209,7 +209,8 @@ Tightening unrestricted entry targets landed in **F-011**.
- `binary` → `androidBinary` with explicit deps on root-app + feature api/impl
(legal under composition-root allowlist; F-011)
-See `docs/ARCHITECTURE.md` §2.2 and §3 for the module map.
+**Gold-standard guide:** [`SAMPLE-APP.md`](SAMPLE-APP.md) (F-014). See also
+`docs/ARCHITECTURE.md` §2.2 and §3 for the module map.
---
diff --git a/docs/PROGRESS.md b/docs/PROGRESS.md
index 520bd987..c7891d5e 100644
--- a/docs/PROGRESS.md
+++ b/docs/PROGRESS.md
@@ -2,6 +2,28 @@
Newest entries first.
+## 2026-07-12 — F-014 Sample app gold-standard multi-feature structure
+
+- **Ticket:** F-014 → `done`
+- **Branch:** `forma/F-014-sample-gold-standard` (from `origin/v2`)
+- **Sample modernization:**
+ - Relocated leftover `com/stepango/blockme/…` source trees under
+ `tools/forma/sample/…` (mvvm lifecycle, extensions, Marvel service/repo/mapper/tests)
+ - Aligned `packageName` with module path for home/characters api+res+viewbinding,
+ characters core api, root-res (`tools.forma.sample.root.res`)
+ - Moved `toggle-widget` sources into `tools.forma.sample.widget.toggle` (+ menu XML)
+ - Home viewbinding UI types under `…home.viewbinding.ui`; list contracts under
+ `…list.viewbinding.domain.model` (impl imports updated)
+ - Normalized `feature/characters/list/impl` deps to consistent `target(":…")` form
+ - Placeholder `FeatureHomeApi` / `FeatureCharactersListApi` objects for empty api modules
+- **Docs:** `docs/SAMPLE-APP.md` gold-standard guide; ARCHITECTURE §3, DEPENDENCY-MATRIX,
+ README pointers; TICKETS status
+- **Verify (real tool output, OpenJDK 17 + SDK 34):**
+ - `application/`: `./gradlew build` → **BUILD SUCCESSFUL** (2112 tasks, 54s after clean)
+- **Commits/PRs:** this run — push + PR base `v2`
+- **Blockers:** none
+- **Next step:** F-015 Android project tutorial (getting started)
+
## 2026-07-11 — F-013 Compose support (GH #96)
- **Ticket:** F-013 → `done`
diff --git a/docs/SAMPLE-APP.md b/docs/SAMPLE-APP.md
new file mode 100644
index 00000000..306ada07
--- /dev/null
+++ b/docs/SAMPLE-APP.md
@@ -0,0 +1,122 @@
+# Sample Android app — gold-standard multi-feature structure (F-014)
+
+The `application/` composite build is Forma’s **product reference**: a multi-feature
+Android app wired with the full target set (`api` / `impl` / `res` / `viewbinding` /
+`widget` / `composeWidget` / composition roots).
+
+Use this layout when teaching Forma (F-015) or copying a starter structure.
+
+## Layout
+
+```
+application/
+├── binary/ androidBinary (APK / composition root)
+├── root-app/ androidApp (Application + root DI)
+├── root-res/ androidRes (launcher, app nav host layout)
+├── toggle-widget/ widget (theme toggle action view)
+├── core/
+│ ├── di/library androidLibrary (Dagger scopes / base component)
+│ ├── mvvm/library androidLibrary (ViewModel helpers, adapters)
+│ ├── navigation/library androidLibrary (feature nav graphs + safe-args)
+│ ├── network/library library (JVM) (Retrofit / Config / NetworkState)
+│ └── theme/{android-util,res}
+├── common/
+│ ├── util util
+│ ├── extensions/{util,android-util}
+│ ├── greeting/compose-widget composeWidget (Compose sample — F-013)
+│ ├── placeholder/res
+│ ├── progressbar/{res,viewbinding}
+│ └── recyclerview/widget
+└── feature/
+ ├── home/{api,impl,res,viewbinding}
+ └── characters/
+ ├── core/{api,impl} (shared domain + Marvel API)
+ ├── list/{api,impl,res,viewbinding}
+ ├── detail/{api,impl,res,viewbinding}
+ └── favorite/{api,impl,res,viewbinding}
+```
+
+Includer discovers every `build.gradle.kts` under `application/`
+(`arbitraryBuildScriptNames = true`). Project path → name uses `-` instead of `/`
+(e.g. `feature/characters/list/impl` → `:feature-characters-list-impl` for Gradle
+type-safe accessors; Forma `target(":feature:characters:list:impl")` uses colon
+path form).
+
+## Feature slice convention
+
+| Slice | Target | Responsibility |
+|-------|--------|----------------|
+| `api` | `api` | JVM contracts, DI feature interfaces, domain models visible to other features |
+| `impl` | `impl` | Android feature implementation (UI, ViewModels, Dagger feature components) |
+| `res` | `androidRes` | Feature strings / dimens / drawables only |
+| `viewbinding` | `viewBinding` | Layouts + light binding-facing types (interfaces for list/favorite UI contracts) |
+
+Rules enforced by validators ([DEPENDENCY-MATRIX.md](DEPENDENCY-MATRIX.md)):
+
+- **`impl` cannot depend on another `impl`** (Dagger-friendly). Cross-feature code
+ goes through `api` (or shared `core` libraries).
+- **Composition roots** (`androidApp` / `androidBinary`) pull feature `api` + `impl`
+ explicitly — do not rely on transitive feature wiring alone.
+- **`api` stays free of `res/`** under `src/main`.
+
+## Package naming
+
+Namespace / `packageName` follows the module path under `tools.forma.sample…`:
+
+| Module path | `packageName` |
+|-------------|-----------------|
+| `feature/home/api` | `tools.forma.sample.feature.home.api` |
+| `feature/characters/list/impl` | `tools.forma.sample.feature.characters.list.impl` |
+| `feature/characters/list/res` | `tools.forma.sample.feature.characters.list.res` |
+| `feature/characters/favorite/viewbinding` | `tools.forma.sample.feature.characters.favorite.viewbinding` |
+| `root-res` | `tools.forma.sample.root.res` |
+| `toggle-widget` | `tools.forma.sample.widget.toggle` |
+| `binary` | `tools.forma.sample.app` |
+
+Kotlin sources live under `src/main/java//…` matching the
+declared package (no leftover third-party roots).
+
+Shared UI contracts that belong with layouts may live in the `viewbinding`
+module package (e.g. list view-state interfaces under
+`…list.viewbinding.domain.model`) so `impl` depends on them without putting
+layouts into `impl`.
+
+## Wiring sketch
+
+```
+binary (androidBinary)
+ └── root-app (androidApp) ── root-res
+ └── feature/*/api + feature/*/impl (explicit)
+ └── common/greeting/compose-widget (Compose demo)
+ └── core/* shared libraries
+
+root-app hosts SampleApp DI:
+ BaseComponent + ThemeComponent + CharactersCore + CharacterFavorite
+
+home/impl hosts bottom navigation:
+ navigation_characters_list_graph + navigation_character_favorite_graph
+ (from core/navigation/library)
+```
+
+## Build
+
+```bash
+source scripts/env-mac.sh
+printf 'sdk.dir=%s\n' "$ANDROID_HOME" > application/local.properties
+cd application && ./gradlew build
+```
+
+Toolchain: AGP **8.1.2**, compileSdk **34**, targetSdk **33**, Gradle **8.4**,
+JDK **17**. See [ENV.md](ENV.md), [COMPOSE.md](COMPOSE.md), [ARCHITECTURE.md](ARCHITECTURE.md).
+
+## What “gold standard” means here
+
+1. **One feature = one folder** with consistent `api` / `impl` / `res` / `viewbinding` slices.
+2. **Aligned packageNames** with directory + Kotlin packages.
+3. **Explicit composition-root deps** on every feature entrypoint.
+4. **Shared platform code** under `core/` and `common/`, not copied per feature.
+5. **Modern targets** represented: View widgets + Compose widget side by side.
+6. **Documented** enough to copy without reading every `build.gradle.kts`.
+
+Not in scope for F-014: full navigation redesign (GH #46), publish path (F-016),
+or configuration-time performance (F-017).