The UI layer has grown organically across two parallel package trees that are now inconsistent with each other:
designsystem/ — holds token definitions (tokens/), the Material theme (theme/), and a set of structural shell components (components/), such as AppTopBar, AppBottomNavigation, AppDrawerContent, PrimaryButton, BottomLinedTitleField, and NavigationItems.ui/ — holds feature screens and their co-located components, but also a flat ui/components/ folder containing domain-aware shared components (CategoryDropdown, ContextDropdown, Pills, MetadataComponents, etc.).navigation/ — sits at the top level of the package: NavGraph.kt (the monolithic locusFlowNavGraph extension function) and NavigationRoutes.kt.This fragmentation creates three problems:
designsystem/components/ and ui/components/ arbitrarily.NavGraph.kt). As the number of navigatable destinations grows, this file becomes a maintenance bottleneck and makes feature ownership harder to trace.inbox, reflection, reference, morning) have no State file and no per-feature navigation fragment, making them structurally inconsistent with heavier features like edititem, processing, nextactions, and someday.A uniform, feature-first UI package layout is needed before the feature count grows further.
We will reorganise all UI source files under a single
ui/root package with four canonical subdirectories —navigation,features,components, andtheme— and we will dissolve thedesignsystem/package into those subdirectories.
ui/
├── navigation/
│ ├── NavGraph.kt (orchestrator — calls feature extensions only)
│ └── NavigationRoutes.kt
│
├── features/
│ ├── edititem/
│ │ ├── EditItemScreen.kt
│ │ ├── EditItemState.kt
│ │ ├── EditItemViewModel.kt
│ │ ├── EditItemNavigation.kt ← new NavGraphBuilder extension
│ │ └── components/
│ │ ├── AnimatedContentField.kt
│ │ ├── ClassificationRow.kt
│ │ └── EditItemFormContent.kt
│ ├── inbox/
│ │ ├── InboxScreen.kt
│ │ ├── InboxViewModel.kt
│ │ └── InboxNavigation.kt ← new NavGraphBuilder extension
│ ├── llm/
│ │ ├── ModelDownloadScreen.kt
│ │ ├── ModelDownloadViewModel.kt
│ │ ├── ModelStatusBanner.kt
│ │ └── LlmNavigation.kt ← new NavGraphBuilder extension
│ ├── morning/
│ │ ├── MorningBriefScreen.kt
│ │ ├── MorningBriefState.kt
│ │ ├── MorningBriefViewModel.kt
│ │ └── MorningBriefNavigation.kt ← new NavGraphBuilder extension
│ ├── nextactions/
│ │ ├── NextActionsScreen.kt
│ │ ├── NextActionsViewModel.kt
│ │ ├── NextActionsNavigation.kt ← new NavGraphBuilder extension
│ │ └── components/
│ │ ├── FilterSortControls.kt
│ │ ├── NextActionsFilterDropdown.kt
│ │ ├── NextActionsSearchBar.kt
│ │ ├── ProcessedItemCard.kt
│ │ ├── ScrollableActionsList.kt
│ │ └── SearchFilterRow.kt
│ ├── processing/
│ │ ├── ProcessingScreen.kt
│ │ ├── ProcessingState.kt
│ │ ├── ProcessingViewModel.kt
│ │ ├── ProcessingNavigation.kt ← new NavGraphBuilder extension
│ │ └── components/
│ │ ├── ActionableBoxes.kt
│ │ ├── ButtonsRow.kt
│ │ ├── EmptyProcessingState.kt
│ │ ├── IconTextButton.kt
│ │ ├── InnerCard.kt
│ │ ├── NonActionableBoxes.kt
│ │ ├── ProcessingCard.kt
│ │ ├── ProcessingOption.kt
│ │ └── SwipeableCardStack.kt
│ ├── reference/
│ │ ├── ReferenceScreen.kt
│ │ ├── ReferenceViewModel.kt
│ │ └── ReferenceNavigation.kt ← new NavGraphBuilder extension
│ ├── reflection/
│ │ ├── DailyReflectionScreen.kt
│ │ ├── DailyReflectionViewModel.kt
│ │ └── DailyReflectionNavigation.kt ← new NavGraphBuilder extension
│ └── someday/
│ ├── SomedayMaybeScreen.kt
│ ├── SomedayMaybeViewModel.kt
│ ├── SomedayMaybeNavigation.kt ← new NavGraphBuilder extension
│ └── components/
│ ├── SomedayFilterDropdown.kt
│ ├── SomedayFilterSortControls.kt
│ ├── SomedayItemCard.kt
│ ├── SomedayScrollableList.kt
│ ├── SomedaySearchBar.kt
│ └── SomedaySearchFilterRow.kt
│
├── components/ (all shared / cross-feature composables)
│ │ ── From designsystem/components/
│ ├── AppBottomNavigation.kt
│ ├── AppDrawerContent.kt
│ ├── AppTopBar.kt
│ ├── BottomLinedTitleField.kt
│ ├── NavigationItems.kt
│ ├── PrimaryButton.kt
│ │ ── From ui/components/
│ ├── CategoryDropdown.kt
│ ├── ContextDropdown.kt
│ ├── EditableTagDropdown.kt
│ ├── MetadataComponents.kt
│ ├── MultiSelectDropdown.kt
│ ├── Pills.kt
│ ├── PlaceholderScreen.kt
│ ├── SingleSelectDropdown.kt
│ └── TagDropdown.kt
│
└── theme/ (Material theme + all design tokens)
│ ── From designsystem/theme/
├── AppTheme.kt
│ ── From designsystem/tokens/
├── ColorTokens.kt
├── Motion.kt
├── ShapeTokens.kt
├── Spacing.kt
└── TypographyTokens.kt
Each composable(...) block currently inside NavGraphBuilder.locusFlowNavGraph is extracted into a NavGraphBuilder extension function in a *Navigation.kt file co-located with the owning feature package. The locusFlowNavGraph body is then reduced to a sequence of calls to those extensions.
Example — EditItemNavigation.kt:
// ui/features/edititem/EditItemNavigation.kt
fun NavGraphBuilder.editItemNavigation(navController: NavHostController) {
composable(
route = NavigationRoutes.EDIT_ITEM,
arguments = listOf(navArgument("itemId") { type = NavType.LongType }),
) {
val viewModel: EditItemViewModel = hiltViewModel()
EditItemScreen(
viewModel = viewModel,
onNavigateBack = { navController.popBackStack() },
onItemDeleted = { navController.popBackStack() },
)
}
}
Resulting locusFlowNavGraph body:
fun NavGraphBuilder.locusFlowNavGraph(navController: NavHostController) {
inboxNavigation(navController)
nextActionsNavigation(navController)
processingNavigation(navController)
dailyReflectionNavigation(navController)
somedayMaybeNavigation(navController)
referenceNavigation(navController)
morningBriefNavigation(navController)
llmNavigation(navController)
editItemNavigation(navController)
// placeholder destinations remain here until promoted to features
composable(NavigationRoutes.VISION) { PlaceholderScreen("Personal Vision") }
composable(NavigationRoutes.SETTINGS) { PlaceholderScreen("Settings Screen") }
composable(NavigationRoutes.PROJECTS) { PlaceholderScreen("Projects Screen") }
}
designsystem/ package is fully dissolved; no files remain there after the migration.navigation/ top-level package becomes ui/navigation/.designsystem/components/ into ui/components/ eliminates the ambiguous dual-home problem. The distinction between "design primitive" and "domain-aware shared component" is captured by naming and documentation, not by separate packages.ui/theme/ makes the layering explicit without adding package depth.composable block into a *Navigation.kt file co-located with the feature means that a developer working on a feature can find and modify all of its routing in one place. It also makes NavGraph.kt stable and small, reducing merge conflicts.ui/ root, with clear sub-trees for navigation, features, shared components, and the theme.NavGraph.kt becomes a thin orchestrator; adding a new screen no longer requires editing it beyond adding one call.ui/features/** vs ui/components/** vs ui/theme/** with meaningful semantics.git mv.Vision, Settings, Projects) are implemented, they will each receive their own feature package and a *Navigation.kt following this ADR.designsystem/ as-is and only reorganise ui/ — Rejected. This retains the ambiguity of two component homes and still requires developers to decide which package to use for each new shared composable.:feature:inbox, :ui:components, etc.) — Rejected at this stage. Module boundaries add build complexity and Hilt wiring overhead that is not justified by the current codebase size. A package-level restructure achieves the same ownership clarity and leaves the door open for modules later (ADR-0003, ADR-0005 philosophy retained).ui/ flat layout (no feature subfolders) — Already the current state for some features; rejected because it does not scale beyond ~five screens without name-collision and discoverability problems.NavGraph.kt.designsystem/ folder in commonMain/ (if any shared multiplatform tokens exist there) is out of scope and addressed separately.