Merge branch 'main' of https://github.com/android/nowinandroid into dependabot/gradle/androidx.activity-activity-compose-1.8.2

pull/1127/head
Sergey Pinkevich 3 months ago
commit 12212db6df

@ -1,10 +1,19 @@
Thanks for submitting a pull request. Please include the following information.
_Thanks for submitting a pull request. Please include the following information._
**What I have done and why**
Include a summary of what your pull request contains, and why you have made these changes.
_Include a summary of what your pull request contains, and why you have made these changes._
Fixes #<issue_number_goes_here>
**How I'm testing it**
_Choose at least one:_
- Unit tests
- UI tests
- Screenshot tests
- N/A _(provide justification)_
**Do tests pass?**
- [ ] Run local tests on `DemoDebug` variant: `./gradlew testDemoDebug`
- [ ] Check formatting: `./gradlew --init-script gradle/init.gradle.kts spotlessApply`

@ -17,6 +17,7 @@ jobs:
permissions:
contents: write
pull-requests: write
timeout-minutes: 60
@ -100,12 +101,13 @@ jobs:
commit_message: "🤖 Updates screenshots"
# Run local tests after screenshot tests to avoid wrong UP-TO-DATE. TODO: Ignore screenshots.
- name: Run local tests
- name: Run local tests and create report
if: always()
run: ./gradlew testDemoDebug :lint:test
# Replace task exclusions with `-Pandroidx.baselineprofile.skipgeneration` when
# https://android-review.googlesource.com/c/platform/frameworks/support/+/2602790 landed in a
# release build
- name: Build all build type and flavor permutations
run: ./gradlew :app:assemble :benchmarks:assemble
-x pixel6Api33ProdNonMinifiedReleaseAndroidTest
@ -119,11 +121,11 @@ jobs:
name: APKs
path: '**/build/outputs/apk/**/*.apk'
- name: Upload test results (XML)
- name: Upload JVM local results (XML)
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
name: local-test-results
path: '**/build/test-results/test*UnitTest/**.xml'
- name: Check lint
@ -180,10 +182,7 @@ jobs:
- name: Setup Gradle
uses: gradle/gradle-build-action@v3
- name: Build projects before running emulator
run: ./gradlew packageDemoDebug packageDemoDebugAndroidTest
- name: Run instrumentation tests
- name: Build projects and run instrumentation tests
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ matrix.api-level }}
@ -193,9 +192,41 @@ jobs:
heap-size: 600M
script: ./gradlew connectedDemoDebugAndroidTest --daemon
- name: Run local tests (including Roborazzi) for the combined coverage report (only API 30)
if: matrix.api-level == 30
# There is no need to verify Roborazzi tests to generate coverage.
run: ./gradlew testDemoDebugUnitTest -Proborazzi.test.verify=false # Add Prod if we ever add JVM tests for prod
# Add `createProdDebugUnitTestCoverageReport` if we ever add JVM tests for prod
- name: Generate coverage reports for Debug variants (only API 30)
if: matrix.api-level == 30
run: ./gradlew createDemoDebugCombinedCoverageReport
- name: Upload test reports
if: always()
uses: actions/upload-artifact@v4
with:
name: test-reports-${{ matrix.api-level }}
path: '**/build/reports/androidTests'
- name: Display local test coverage (only API 30)
if: matrix.api-level == 30
id: jacoco
uses: madrapps/jacoco-report@v1.6.1
with:
title: Combined test coverage report
min-coverage-overall: 40
min-coverage-changed-files: 60
paths: |
${{ github.workspace }}/**/build/reports/jacoco/**/*Report.xml
token: ${{ secrets.GITHUB_TOKEN }}
- name: Upload local coverage reports (XML + HTML) (only API 30)
if: matrix.api-level == 30
uses: actions/upload-artifact@v4
with:
name: coverage-reports
if-no-files-found: error
compression-level: 1
overwrite: false
path: '**/build/reports/jacoco/'

@ -1,3 +1,3 @@
# :app-nia-catalog module
![Dependency graph](../docs/images/graphs/dep_graph_app_nia_catalog.png)
## Dependency graph
![Dependency graph](../docs/images/graphs/dep_graph_app_nia_catalog.svg)

@ -59,7 +59,7 @@ android {
// To publish on the Play store a private signing key is required, but to allow anyone
// who clones the code to sign and run the release variant, use the debug signing key.
// TODO: Abstract the signing configuration to a separate file to avoid hardcoding this.
signingConfig = signingConfigs.getByName("debug")
signingConfig = signingConfigs.named("debug").get()
}
}
}

@ -1,3 +1,3 @@
# :app module
![Dependency graph](../docs/images/graphs/dep_graph_app.png)
## Dependency graph
![Dependency graph](../docs/images/graphs/dep_graph_app.svg)

@ -21,7 +21,6 @@ plugins {
alias(libs.plugins.nowinandroid.android.application.flavors)
alias(libs.plugins.nowinandroid.android.application.jacoco)
alias(libs.plugins.nowinandroid.android.hilt)
id("jacoco")
alias(libs.plugins.nowinandroid.android.application.firebase)
id("com.google.android.gms.oss-licenses-plugin")
alias(libs.plugins.baselineprofile)
@ -53,7 +52,7 @@ android {
// To publish on the Play store a private signing key is required, but to allow anyone
// who clones the code to sign and run the release variant, use the debug signing key.
// TODO: Abstract the signing configuration to a separate file to avoid hardcoding this.
signingConfig = signingConfigs.getByName("debug")
signingConfig = signingConfigs.named("debug").get()
// Ensure Baseline Profile is fresh for release builds.
baselineProfile.automaticGenerationDuringBuild = true
}
@ -101,6 +100,7 @@ dependencies {
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.tracing.ktx)
implementation(libs.androidx.window.core)
implementation(libs.kotlinx.coroutines.guava)
implementation(libs.coil.kt)

@ -2,8 +2,8 @@ androidx.activity:activity-compose:1.8.0
androidx.activity:activity-ktx:1.8.0
androidx.activity:activity:1.8.0
androidx.annotation:annotation-experimental:1.4.0
androidx.annotation:annotation-jvm:1.7.1
androidx.annotation:annotation:1.7.1
androidx.annotation:annotation-jvm:1.8.0-beta01
androidx.annotation:annotation:1.8.0-beta01
androidx.appcompat:appcompat-resources:1.6.1
androidx.appcompat:appcompat:1.6.1
androidx.arch.core:core-common:2.2.0
@ -13,20 +13,20 @@ androidx.browser:browser:1.8.0
androidx.collection:collection-jvm:1.4.0
androidx.collection:collection-ktx:1.4.0
androidx.collection:collection:1.4.0
androidx.compose.animation:animation-android:1.6.3
androidx.compose.animation:animation-core-android:1.6.3
androidx.compose.animation:animation-core:1.6.3
androidx.compose.animation:animation:1.6.3
androidx.compose.animation:animation-android:1.7.0-alpha06
androidx.compose.animation:animation-core-android:1.7.0-alpha06
androidx.compose.animation:animation-core:1.7.0-alpha06
androidx.compose.animation:animation:1.7.0-alpha06
androidx.compose.foundation:foundation-android:1.6.3
androidx.compose.foundation:foundation-layout-android:1.6.3
androidx.compose.foundation:foundation-layout:1.6.3
androidx.compose.foundation:foundation:1.6.3
androidx.compose.material3.adaptive:adaptive-android:1.0.0-alpha08
androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0-alpha08
androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha08
androidx.compose.material3.adaptive:adaptive-navigation-android:1.0.0-alpha08
androidx.compose.material3.adaptive:adaptive-navigation:1.0.0-alpha08
androidx.compose.material3.adaptive:adaptive:1.0.0-alpha08
androidx.compose.material3.adaptive:adaptive-android:1.0.0-alpha10
androidx.compose.material3.adaptive:adaptive-layout-android:1.0.0-alpha10
androidx.compose.material3.adaptive:adaptive-layout:1.0.0-alpha10
androidx.compose.material3.adaptive:adaptive-navigation-android:1.0.0-alpha10
androidx.compose.material3.adaptive:adaptive-navigation:1.0.0-alpha10
androidx.compose.material3.adaptive:adaptive:1.0.0-alpha10
androidx.compose.material3:material3-android:1.2.1
androidx.compose.material3:material3-window-size-class-android:1.2.1
androidx.compose.material3:material3-window-size-class:1.2.1
@ -37,25 +37,25 @@ androidx.compose.material:material-icons-extended-android:1.6.3
androidx.compose.material:material-icons-extended:1.6.3
androidx.compose.material:material-ripple-android:1.6.3
androidx.compose.material:material-ripple:1.6.3
androidx.compose.runtime:runtime-android:1.6.3
androidx.compose.runtime:runtime-saveable-android:1.6.3
androidx.compose.runtime:runtime-saveable:1.6.3
androidx.compose.runtime:runtime-android:1.7.0-alpha06
androidx.compose.runtime:runtime-saveable-android:1.7.0-alpha06
androidx.compose.runtime:runtime-saveable:1.7.0-alpha06
androidx.compose.runtime:runtime-tracing:1.0.0-beta01
androidx.compose.runtime:runtime:1.6.3
androidx.compose.ui:ui-android:1.6.3
androidx.compose.ui:ui-geometry-android:1.6.3
androidx.compose.ui:ui-geometry:1.6.3
androidx.compose.ui:ui-graphics-android:1.6.3
androidx.compose.ui:ui-graphics:1.6.3
androidx.compose.ui:ui-text-android:1.6.3
androidx.compose.ui:ui-text:1.6.3
androidx.compose.ui:ui-tooling-preview-android:1.6.3
androidx.compose.ui:ui-tooling-preview:1.6.3
androidx.compose.ui:ui-unit-android:1.6.3
androidx.compose.ui:ui-unit:1.6.3
androidx.compose.ui:ui-util-android:1.6.3
androidx.compose.ui:ui-util:1.6.3
androidx.compose.ui:ui:1.6.3
androidx.compose.runtime:runtime:1.7.0-alpha06
androidx.compose.ui:ui-android:1.7.0-alpha06
androidx.compose.ui:ui-geometry-android:1.7.0-alpha06
androidx.compose.ui:ui-geometry:1.7.0-alpha06
androidx.compose.ui:ui-graphics-android:1.7.0-alpha06
androidx.compose.ui:ui-graphics:1.7.0-alpha06
androidx.compose.ui:ui-text-android:1.7.0-alpha06
androidx.compose.ui:ui-text:1.7.0-alpha06
androidx.compose.ui:ui-tooling-preview-android:1.7.0-alpha06
androidx.compose.ui:ui-tooling-preview:1.7.0-alpha06
androidx.compose.ui:ui-unit-android:1.7.0-alpha06
androidx.compose.ui:ui-unit:1.7.0-alpha06
androidx.compose.ui:ui-util-android:1.7.0-alpha06
androidx.compose.ui:ui-util:1.7.0-alpha06
androidx.compose.ui:ui:1.7.0-alpha06
androidx.compose:compose-bom:2024.02.02
androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.12.0
@ -74,26 +74,32 @@ androidx.emoji2:emoji2-views-helper:1.3.0
androidx.emoji2:emoji2:1.3.0
androidx.exifinterface:exifinterface:1.3.7
androidx.fragment:fragment:1.5.1
androidx.graphics:graphics-path:1.0.0-beta02
androidx.hilt:hilt-common:1.1.0
androidx.hilt:hilt-navigation-compose:1.2.0
androidx.hilt:hilt-navigation:1.2.0
androidx.hilt:hilt-work:1.1.0
androidx.interpolator:interpolator:1.0.0
androidx.legacy:legacy-support-core-utils:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.7.0
androidx.lifecycle:lifecycle-common:2.7.0
androidx.lifecycle:lifecycle-livedata-core-ktx:2.7.0
androidx.lifecycle:lifecycle-livedata-core:2.7.0
androidx.lifecycle:lifecycle-livedata:2.7.0
androidx.lifecycle:lifecycle-process:2.7.0
androidx.lifecycle:lifecycle-runtime-compose:2.7.0
androidx.lifecycle:lifecycle-runtime-ktx:2.7.0
androidx.lifecycle:lifecycle-runtime:2.7.0
androidx.lifecycle:lifecycle-service:2.7.0
androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0
androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.7.0
androidx.lifecycle:lifecycle-viewmodel:2.7.0
androidx.lifecycle:lifecycle-common-java8:2.8.0-alpha04
androidx.lifecycle:lifecycle-common-jvm:2.8.0-alpha04
androidx.lifecycle:lifecycle-common:2.8.0-alpha04
androidx.lifecycle:lifecycle-livedata-core-ktx:2.8.0-alpha04
androidx.lifecycle:lifecycle-livedata-core:2.8.0-alpha04
androidx.lifecycle:lifecycle-livedata:2.8.0-alpha04
androidx.lifecycle:lifecycle-process:2.8.0-alpha04
androidx.lifecycle:lifecycle-runtime-android:2.8.0-alpha04
androidx.lifecycle:lifecycle-runtime-compose:2.8.0-alpha04
androidx.lifecycle:lifecycle-runtime-ktx-android:2.8.0-alpha04
androidx.lifecycle:lifecycle-runtime-ktx:2.8.0-alpha04
androidx.lifecycle:lifecycle-runtime:2.8.0-alpha04
androidx.lifecycle:lifecycle-service:2.8.0-alpha04
androidx.lifecycle:lifecycle-viewmodel-android:2.8.0-alpha04
androidx.lifecycle:lifecycle-viewmodel-compose-android:2.8.0-alpha04
androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0-alpha04
androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0-alpha04
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.8.0-alpha04
androidx.lifecycle:lifecycle-viewmodel:2.8.0-alpha04
androidx.loader:loader:1.0.0
androidx.localbroadcastmanager:localbroadcastmanager:1.0.0
androidx.metrics:metrics-performance:1.0.0-alpha04
@ -123,14 +129,14 @@ androidx.vectordrawable:vectordrawable:1.1.0
androidx.versionedparcelable:versionedparcelable:1.1.1
androidx.viewpager:viewpager:1.0.0
androidx.window.extensions.core:core:1.0.0
androidx.window:window-core-android:1.3.0-alpha02
androidx.window:window-core:1.3.0-alpha02
androidx.window:window:1.3.0-alpha02
androidx.window:window-core-android:1.3.0-alpha03
androidx.window:window-core:1.3.0-alpha03
androidx.window:window:1.3.0-alpha03
androidx.work:work-runtime-ktx:2.9.0
androidx.work:work-runtime:2.9.0
com.caverock:androidsvg-aar:1.4
com.google.accompanist:accompanist-drawablepainter:0.32.0
com.google.accompanist:accompanist-permissions:0.32.0
com.google.accompanist:accompanist-permissions:0.34.0
com.google.android.datatransport:transport-api:3.0.0
com.google.android.datatransport:transport-backend-cct:3.1.9
com.google.android.datatransport:transport-runtime:3.1.9
@ -195,8 +201,6 @@ io.coil-kt:coil-compose-base:2.6.0
io.coil-kt:coil-compose:2.6.0
io.coil-kt:coil-svg:2.6.0
io.coil-kt:coil:2.6.0
io.github.aakira:napier-android:1.4.1
io.github.aakira:napier:1.4.1
javax.inject:javax.inject:1
org.checkerframework:checker-qual:3.12.0
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22

@ -6,9 +6,9 @@ uses-permission: name='android.permission.ACCESS_NETWORK_STATE'
uses-permission: name='android.permission.POST_NOTIFICATIONS'
uses-permission: name='android.permission.WAKE_LOCK'
uses-permission: name='com.google.android.c2dm.permission.RECEIVE'
uses-permission: name='com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE'
uses-permission: name='android.permission.RECEIVE_BOOT_COMPLETED'
uses-permission: name='android.permission.FOREGROUND_SERVICE'
uses-permission: name='com.google.android.finsky.permission.BIND_GET_INSTALL_REFERRER_SERVICE'
uses-permission: name='com.google.samples.apps.nowinandroid.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION'
application-label:'Now in Android'
application-label-af:'Now in Android'
@ -105,9 +105,9 @@ application-icon-640:'res/mipmap-anydpi-v26/ic_launcher.xml'
application-icon-65534:'res/mipmap-anydpi-v26/ic_launcher.xml'
application: label='Now in Android' icon='res/mipmap-anydpi-v26/ic_launcher.xml'
launchable-activity: name='com.google.samples.apps.nowinandroid.MainActivity' label='' icon=''
uses-library-not-required:'android.ext.adservices'
uses-library-not-required:'androidx.window.extensions'
uses-library-not-required:'androidx.window.sidecar'
uses-library-not-required:'android.ext.adservices'
feature-group: label=''
uses-feature: name='android.hardware.faketouch'
uses-implied-feature: name='android.hardware.faketouch' reason='default feature for all apps'
@ -119,3 +119,4 @@ supports-screens: 'small' 'normal' 'large' 'xlarge'
supports-any-density: 'true'
locales: '--_--' 'af' 'am' 'ar' 'as' 'az' 'be' 'bg' 'bn' 'bs' 'ca' 'cs' 'da' 'de' 'el' 'en-AU' 'en-CA' 'en-GB' 'en-IN' 'en-XC' 'es' 'es-US' 'et' 'eu' 'fa' 'fi' 'fr' 'fr-CA' 'gl' 'gu' 'hi' 'hr' 'hu' 'hy' 'in' 'is' 'it' 'iw' 'ja' 'ka' 'kk' 'km' 'kn' 'ko' 'ky' 'lo' 'lt' 'lv' 'mk' 'ml' 'mn' 'mr' 'ms' 'my' 'nb' 'ne' 'nl' 'or' 'pa' 'pl' 'pt' 'pt-BR' 'pt-PT' 'ro' 'ru' 'si' 'sk' 'sl' 'sq' 'sr' 'sr-Latn' 'sv' 'sw' 'ta' 'te' 'th' 'tl' 'tr' 'uk' 'ur' 'uz' 'vi' 'zh-CN' 'zh-HK' 'zh-TW' 'zu'
densities: '120' '160' '240' '320' '480' '640' '65534'
native-code: 'arm64-v8a' 'armeabi-v7a' 'x86' 'x86_64'

@ -17,7 +17,6 @@
package com.google.samples.apps.nowinandroid.ui
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.WindowInsetsSides
@ -76,11 +75,6 @@ import com.google.samples.apps.nowinandroid.navigation.NiaNavHost
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination
import com.google.samples.apps.nowinandroid.feature.settings.R as settingsR
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalLayoutApi::class,
ExperimentalComposeUiApi::class,
)
@Composable
fun NiaApp(appState: NiaAppState) {
val shouldShowGradientBackground =
@ -110,95 +104,122 @@ fun NiaApp(appState: NiaAppState) {
}
}
if (showSettingsDialog) {
SettingsDialog(
onDismiss = { showSettingsDialog = false },
NiaApp(
appState = appState,
snackbarHostState = snackbarHostState,
showSettingsDialog = showSettingsDialog,
onSettingsDismissed = { showSettingsDialog = false },
onTopAppBarActionClick = { showSettingsDialog = true },
)
}
}
}
@Composable
@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
internal fun NiaApp(
appState: NiaAppState,
snackbarHostState: SnackbarHostState,
showSettingsDialog: Boolean,
onSettingsDismissed: () -> Unit,
onTopAppBarActionClick: () -> Unit,
modifier: Modifier = Modifier,
) {
val unreadDestinations by appState.topLevelDestinationsWithUnreadResources
.collectAsStateWithLifecycle()
if (showSettingsDialog) {
SettingsDialog(
onDismiss = { onSettingsDismissed() },
)
}
Scaffold(
modifier = modifier.semantics {
testTagsAsResourceId = true
},
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground,
contentWindowInsets = WindowInsets(0, 0, 0, 0),
snackbarHost = { SnackbarHost(snackbarHostState) },
bottomBar = {
if (appState.shouldShowBottomBar) {
NiaBottomBar(
destinations = appState.topLevelDestinations,
destinationsWithUnreadResources = unreadDestinations,
onNavigateToDestination = appState::navigateToTopLevelDestination,
currentDestination = appState.currentDestination,
modifier = Modifier.testTag("NiaBottomBar"),
)
}
},
) { padding ->
Row(
Modifier
.fillMaxSize()
.padding(padding)
.consumeWindowInsets(padding)
.windowInsetsPadding(
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal,
),
),
) {
if (appState.shouldShowNavRail) {
NiaNavRail(
destinations = appState.topLevelDestinations,
destinationsWithUnreadResources = unreadDestinations,
onNavigateToDestination = appState::navigateToTopLevelDestination,
currentDestination = appState.currentDestination,
modifier = Modifier
.testTag("NiaNavRail")
.safeDrawingPadding(),
)
}
val unreadDestinations by appState.topLevelDestinationsWithUnreadResources.collectAsStateWithLifecycle()
Scaffold(
modifier = Modifier.semantics {
testTagsAsResourceId = true
},
containerColor = Color.Transparent,
contentColor = MaterialTheme.colorScheme.onBackground,
contentWindowInsets = WindowInsets(0, 0, 0, 0),
snackbarHost = { SnackbarHost(snackbarHostState) },
bottomBar = {
if (appState.shouldShowBottomBar) {
NiaBottomBar(
destinations = appState.topLevelDestinations,
destinationsWithUnreadResources = unreadDestinations,
onNavigateToDestination = appState::navigateToTopLevelDestination,
currentDestination = appState.currentDestination,
modifier = Modifier.testTag("NiaBottomBar"),
)
}
},
) { padding ->
Row(
Modifier
.fillMaxSize()
.padding(padding)
.consumeWindowInsets(padding)
.windowInsetsPadding(
WindowInsets.safeDrawing.only(
WindowInsetsSides.Horizontal,
),
Column(Modifier.fillMaxSize()) {
// Show the top app bar on top level destinations.
val destination = appState.currentTopLevelDestination
val shouldShowTopAppBar = destination != null
if (destination != null) {
NiaTopAppBar(
titleRes = destination.titleTextId,
navigationIcon = NiaIcons.Search,
navigationIconContentDescription = stringResource(
id = settingsR.string.feature_settings_top_app_bar_navigation_icon_description,
),
) {
if (appState.shouldShowNavRail) {
NiaNavRail(
destinations = appState.topLevelDestinations,
destinationsWithUnreadResources = unreadDestinations,
onNavigateToDestination = appState::navigateToTopLevelDestination,
currentDestination = appState.currentDestination,
modifier = Modifier
.testTag("NiaNavRail")
.safeDrawingPadding(),
)
}
Column(Modifier.fillMaxSize()) {
// Show the top app bar on top level destinations.
val destination = appState.currentTopLevelDestination
if (destination != null) {
NiaTopAppBar(
titleRes = destination.titleTextId,
navigationIcon = NiaIcons.Search,
navigationIconContentDescription = stringResource(
id = settingsR.string.feature_settings_top_app_bar_navigation_icon_description,
),
actionIcon = NiaIcons.Settings,
actionIconContentDescription = stringResource(
id = settingsR.string.feature_settings_top_app_bar_action_icon_description,
),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent,
),
onActionClick = { showSettingsDialog = true },
onNavigationClick = { appState.navigateToSearch() },
)
}
actionIcon = NiaIcons.Settings,
actionIconContentDescription = stringResource(
id = settingsR.string.feature_settings_top_app_bar_action_icon_description,
),
colors = TopAppBarDefaults.centerAlignedTopAppBarColors(
containerColor = Color.Transparent,
),
onActionClick = { onTopAppBarActionClick() },
onNavigationClick = { appState.navigateToSearch() },
)
}
NiaNavHost(
appState = appState,
onShowSnackbar = { message, action ->
snackbarHostState.showSnackbar(
message = message,
actionLabel = action,
duration = Short,
) == ActionPerformed
},
NiaNavHost(
appState = appState,
onShowSnackbar = { message, action ->
snackbarHostState.showSnackbar(
message = message,
actionLabel = action,
duration = Short,
) == ActionPerformed
},
modifier = if (shouldShowTopAppBar) {
Modifier.consumeWindowInsets(
WindowInsets.safeDrawing.only(WindowInsetsSides.Top),
)
}
// TODO: We may want to add padding or spacer when the snackbar is shown so that
// content doesn't display behind it.
}
} else {
Modifier
},
)
}
// TODO: We may want to add padding or spacer when the snackbar is shown so that
// content doesn't display behind it.
}
}
}

@ -17,8 +17,6 @@
package com.google.samples.apps.nowinandroid.ui.interests2pane
import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Box
import androidx.compose.material3.Text
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
@ -39,6 +37,7 @@ import androidx.navigation.navArgument
import com.google.samples.apps.nowinandroid.feature.interests.InterestsRoute
import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERESTS_ROUTE
import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG
import com.google.samples.apps.nowinandroid.feature.topic.TopicDetailPlaceholder
import com.google.samples.apps.nowinandroid.feature.topic.navigation.TOPIC_ROUTE
import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen
@ -77,7 +76,7 @@ internal fun InterestsListDetailScreen(
selectedTopicId: String?,
onTopicClick: (String) -> Unit,
) {
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator()
BackHandler(listDetailNavigator.canNavigateBack()) {
listDetailNavigator.navigateBack()
}
@ -113,9 +112,7 @@ internal fun InterestsListDetailScreen(
onTopicClick = ::onTopicClickShowDetailPane,
)
composable(route = TOPIC_ROUTE) {
Box {
Text("Placeholder")
}
TopicDetailPlaceholder()
}
}
},

@ -0,0 +1,238 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.ui
import android.util.Log
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.material3.SnackbarDuration.Indefinite
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.windowsizeclass.ExperimentalMaterial3WindowSizeClassApi
import androidx.compose.material3.windowsizeclass.WindowSizeClass
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onRoot
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.dp
import androidx.test.platform.app.InstrumentationRegistry
import androidx.work.Configuration
import androidx.work.testing.SynchronousExecutor
import androidx.work.testing.WorkManagerTestInitHelper
import com.github.takahirom.roborazzi.captureRoboImage
import com.google.accompanist.testharness.TestHarness
import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepository
import com.google.samples.apps.nowinandroid.core.data.repository.UserNewsResourceRepository
import com.google.samples.apps.nowinandroid.core.data.test.repository.FakeUserDataRepository
import com.google.samples.apps.nowinandroid.core.data.util.NetworkMonitor
import com.google.samples.apps.nowinandroid.core.data.util.TimeZoneMonitor
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.core.testing.util.DefaultRoborazziOptions
import com.google.samples.apps.nowinandroid.uitesthiltmanifest.HiltComponentActivity
import dagger.hilt.android.testing.BindValue
import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TemporaryFolder
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.GraphicsMode
import org.robolectric.annotation.LooperMode
import javax.inject.Inject
/**
* Tests that the Snackbar is correctly displayed on different screen sizes.
*/
@OptIn(ExperimentalMaterial3WindowSizeClassApi::class)
@RunWith(RobolectricTestRunner::class)
@GraphicsMode(GraphicsMode.Mode.NATIVE)
// Configure Robolectric to use a very large screen size that can fit all of the test sizes.
// This allows enough room to render the content under test without clipping or scaling.
@Config(application = HiltTestApplication::class, qualifiers = "w1000dp-h1000dp-480dpi")
@LooperMode(LooperMode.Mode.PAUSED)
@HiltAndroidTest
class SnackbarScreenshotTests {
/**
* Manages the components' state and is used to perform injection on your test
*/
@get:Rule(order = 0)
val hiltRule = HiltAndroidRule(this)
/**
* Create a temporary folder used to create a Data Store file. This guarantees that
* the file is removed in between each test, preventing a crash.
*/
@BindValue
@get:Rule(order = 1)
val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
/**
* Use a test activity to set the content on.
*/
@get:Rule(order = 2)
val composeTestRule = createAndroidComposeRule<HiltComponentActivity>()
@Inject
lateinit var networkMonitor: NetworkMonitor
@Inject
lateinit var timeZoneMonitor: TimeZoneMonitor
@Inject
lateinit var userDataRepository: FakeUserDataRepository
@Inject
lateinit var topicsRepository: TopicsRepository
@Inject
lateinit var userNewsResourceRepository: UserNewsResourceRepository
@Before
fun setup() {
val config = Configuration.Builder()
.setMinimumLoggingLevel(Log.DEBUG)
.setExecutor(SynchronousExecutor())
.build()
// Initialize WorkManager for instrumentation tests.
WorkManagerTestInitHelper.initializeTestWorkManager(
InstrumentationRegistry.getInstrumentation().context,
config,
)
hiltRule.inject()
// Configure user data
runBlocking {
userDataRepository.setShouldHideOnboarding(true)
userDataRepository.setFollowedTopicIds(
setOf(topicsRepository.getTopics().first().first().id),
)
}
}
@Test
fun phone_noSnackbar() {
val snackbarHostState = SnackbarHostState()
testSnackbarScreenshotWithSize(
snackbarHostState,
400.dp,
500.dp,
"snackbar_compact_medium_noSnackbar",
action = { },
)
}
@Test
fun snackbarShown_phone() {
val snackbarHostState = SnackbarHostState()
testSnackbarScreenshotWithSize(
snackbarHostState,
400.dp,
500.dp,
"snackbar_compact_medium",
) {
snackbarHostState.showSnackbar(
"This is a test snackbar message",
actionLabel = "Action Label",
duration = Indefinite,
)
}
}
@Test
fun snackbarShown_foldable() {
val snackbarHostState = SnackbarHostState()
testSnackbarScreenshotWithSize(
snackbarHostState,
600.dp,
600.dp,
"snackbar_medium_medium",
) {
snackbarHostState.showSnackbar(
"This is a test snackbar message",
actionLabel = "Action Label",
duration = Indefinite,
)
}
}
@Test
fun snackbarShown_tablet() {
val snackbarHostState = SnackbarHostState()
testSnackbarScreenshotWithSize(
snackbarHostState,
900.dp,
900.dp,
"snackbar_expanded_expanded",
) {
snackbarHostState.showSnackbar(
"This is a test snackbar message",
actionLabel = "Action Label",
duration = Indefinite,
)
}
}
private fun testSnackbarScreenshotWithSize(
snackbarHostState: SnackbarHostState,
width: Dp,
height: Dp,
screenshotName: String,
action: suspend () -> Unit,
) {
lateinit var scope: CoroutineScope
composeTestRule.setContent {
scope = rememberCoroutineScope()
TestHarness(size = DpSize(width, height)) {
BoxWithConstraints {
val appState = rememberNiaAppState(
windowSizeClass = WindowSizeClass.calculateFromSize(
DpSize(maxWidth, maxHeight),
),
networkMonitor = networkMonitor,
userNewsResourceRepository = userNewsResourceRepository,
timeZoneMonitor = timeZoneMonitor,
)
NiaTheme {
NiaApp(appState, snackbarHostState, false, {}, {})
}
}
}
}
scope.launch {
action()
}
composeTestRule.onRoot()
.captureRoboImage(
"src/testDemo/screenshots/$screenshotName.png",
roborazziOptions = DefaultRoborazziOptions,
)
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 156 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

@ -15,6 +15,7 @@
*/
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
import com.google.samples.apps.nowinandroid.configureJacoco
import org.gradle.api.Plugin
import org.gradle.api.Project
@ -23,13 +24,15 @@ import org.gradle.kotlin.dsl.getByType
class AndroidApplicationJacocoConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("org.gradle.jacoco")
apply("com.android.application")
pluginManager.apply("jacoco")
val androidExtension = extensions.getByType<BaseAppModuleExtension>()
androidExtension.buildTypes.configureEach {
enableAndroidTestCoverage = true
enableUnitTestCoverage = true
}
val extension = extensions.getByType<ApplicationAndroidComponentsExtension>()
configureJacoco(extension)
configureJacoco(extensions.getByType<ApplicationAndroidComponentsExtension>())
}
}
}
}

@ -14,6 +14,8 @@
* limitations under the License.
*/
import com.android.build.api.dsl.LibraryExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.api.variant.LibraryAndroidComponentsExtension
import com.google.samples.apps.nowinandroid.configureJacoco
import org.gradle.api.Plugin
@ -23,13 +25,15 @@ import org.gradle.kotlin.dsl.getByType
class AndroidLibraryJacocoConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("org.gradle.jacoco")
apply("com.android.library")
pluginManager.apply("jacoco")
val androidExtension = extensions.getByType<LibraryExtension>()
androidExtension.buildTypes.configureEach {
enableAndroidTestCoverage = true
enableUnitTestCoverage = true
}
val extension = extensions.getByType<LibraryAndroidComponentsExtension>()
configureJacoco(extension)
configureJacoco(extensions.getByType<LibraryAndroidComponentsExtension>())
}
}
}
}

@ -15,6 +15,7 @@
*/
import androidx.room.gradle.RoomExtension
import com.google.devtools.ksp.gradle.KspExtension
import com.google.samples.apps.nowinandroid.libs
import org.gradle.api.Plugin
import org.gradle.api.Project
@ -28,6 +29,10 @@ class AndroidRoomConventionPlugin : Plugin<Project> {
pluginManager.apply("androidx.room")
pluginManager.apply("com.google.devtools.ksp")
extensions.configure<KspExtension> {
arg("room.generateKotlin", "true")
}
extensions.configure<RoomExtension> {
// The schemas directory contains a schema file for each version of the Room database.
// This is required to enable Room auto migrations.

@ -1,5 +1,5 @@
/*
* Copyright 2022 The Android Open Source Project
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -16,8 +16,13 @@
package com.google.samples.apps.nowinandroid
import com.android.build.api.artifact.ScopedArtifact
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.ScopedArtifacts
import org.gradle.api.Project
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.testing.Test
import org.gradle.kotlin.dsl.configure
import org.gradle.kotlin.dsl.register
@ -32,13 +37,24 @@ private val coverageExclusions = listOf(
"**/R.class",
"**/R\$*.class",
"**/BuildConfig.*",
"**/Manifest*.*"
"**/Manifest*.*",
"**/*_Hilt*.class",
"**/Hilt_*.class",
)
private fun String.capitalize() = replaceFirstChar {
if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString()
}
/**
* Creates a new task that generates a combined coverage report with data from local and
* instrumented tests.
*
* `create{variant}CombinedCoverageReport`
*
* Note that coverage data must exist before running the task. This allows us to run device
* tests on CI using a different Github Action or an external device farm.
*/
internal fun Project.configureJacoco(
androidComponentsExtension: AndroidComponentsExtension<*, *, *>,
) {
@ -46,37 +62,53 @@ internal fun Project.configureJacoco(
toolVersion = libs.findVersion("jacoco").get().toString()
}
val jacocoTestReport = tasks.create("jacocoTestReport")
androidComponentsExtension.onVariants { variant ->
val testTaskName = "test${variant.name.capitalize()}UnitTest"
val myObjFactory = project.objects
val buildDir = layout.buildDirectory.get().asFile
val reportTask = tasks.register("jacoco${testTaskName.capitalize()}Report", JacocoReport::class) {
dependsOn(testTaskName)
val allJars: ListProperty<RegularFile> = myObjFactory.listProperty(RegularFile::class.java)
val allDirectories: ListProperty<Directory> = myObjFactory.listProperty(Directory::class.java)
val reportTask =
tasks.register("create${variant.name.capitalize()}CombinedCoverageReport", JacocoReport::class) {
reports {
xml.required.set(true)
html.required.set(true)
}
classDirectories.setFrom(
fileTree("$buildDir/tmp/kotlin-classes/${variant.name}") {
exclude(coverageExclusions)
classDirectories.setFrom(
allJars,
allDirectories.map { dirs ->
dirs.map { dir ->
myObjFactory.fileTree().setDir(dir).exclude(coverageExclusions)
}
}
)
reports {
xml.required.set(true)
html.required.set(true)
}
)
sourceDirectories.setFrom(files("$projectDir/src/main/java", "$projectDir/src/main/kotlin"))
executionData.setFrom(file("$buildDir/jacoco/$testTaskName.exec"))
}
// TODO: This is missing files in src/debug/, src/prod, src/demo, src/demoDebug...
sourceDirectories.setFrom(files("$projectDir/src/main/java", "$projectDir/src/main/kotlin"))
executionData.setFrom(
project.fileTree("$buildDir/outputs/unit_test_code_coverage/${variant.name}UnitTest")
.matching { include("**/*.exec") },
project.fileTree("$buildDir/outputs/code_coverage/${variant.name}AndroidTest")
.matching { include("**/*.ec") }
)
}
jacocoTestReport.dependsOn(reportTask)
variant.artifacts.forScope(ScopedArtifacts.Scope.PROJECT)
.use(reportTask)
.toGet(
ScopedArtifact.CLASSES,
{ _ -> allJars },
{ _ -> allDirectories },
)
}
tasks.withType<Test>().configureEach {
configure<JacocoTaskExtension> {
// Required for JaCoCo + Robolectric
// https://github.com/robolectric/robolectric/issues/2230
// TODO: Consider removing if not we don't add Robolectric
isIncludeNoLocationClasses = true
// Required for JDK 11 with the above

@ -27,9 +27,10 @@ buildscript {
exclude(group = "com.google.protobuf")
}
}
}
// Lists all plugins used throughout the project without applying them.
// Lists all plugins used throughout the project
plugins {
alias(libs.plugins.android.application) apply false
alias(libs.plugins.android.library) apply false
@ -46,4 +47,15 @@ plugins {
alias(libs.plugins.roborazzi) apply false
alias(libs.plugins.secrets) apply false
alias(libs.plugins.room) apply false
alias(libs.plugins.module.graph) apply true // Plugin applied to allow module graph generation
}
// Task to print all the module paths in the project e.g. :core:data
// Used by module graph generator script
tasks.register("printModulePaths") {
subprojects {
if (subprojects.size == 0) {
println(this.path)
}
}
}

@ -0,0 +1,3 @@
# :core:analytics module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_analytics.svg)

@ -1,3 +1,3 @@
# :core:common module
![Dependency graph](../../docs/images/graphs/dep_graph_core_common.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_common.svg)

@ -1,3 +1,3 @@
# :core:data-test module
![Dependency graph](../../docs/images/graphs/dep_graph_core_data_test.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_data_test.svg)

@ -25,7 +25,7 @@ import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import com.google.samples.apps.nowinandroid.core.model.data.NewsResource
import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO
import com.google.samples.apps.nowinandroid.core.network.fake.FakeNiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.network.demo.DemoNiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
@ -39,9 +39,9 @@ import javax.inject.Inject
* This allows us to run the app with fake data, without needing an internet connection or working
* backend.
*/
internal class FakeNewsRepository @Inject constructor(
class FakeNewsRepository @Inject constructor(
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
private val datasource: FakeNiaNetworkDataSource,
private val datasource: DemoNiaNetworkDataSource,
) : NewsRepository {
override fun getNewsResources(

@ -21,7 +21,7 @@ import com.google.samples.apps.nowinandroid.core.data.repository.TopicsRepositor
import com.google.samples.apps.nowinandroid.core.model.data.Topic
import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO
import com.google.samples.apps.nowinandroid.core.network.fake.FakeNiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.network.demo.DemoNiaNetworkDataSource
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@ -38,7 +38,7 @@ import javax.inject.Inject
*/
internal class FakeTopicsRepository @Inject constructor(
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
private val datasource: FakeNiaNetworkDataSource,
private val datasource: DemoNiaNetworkDataSource,
) : TopicsRepository {
override fun getTopics(): Flow<List<Topic>> = flow {
emit(

@ -30,7 +30,7 @@ import javax.inject.Inject
* This allows us to run the app with fake data, without needing an internet connection or working
* backend.
*/
internal class FakeUserDataRepository @Inject constructor(
class FakeUserDataRepository @Inject constructor(
private val niaPreferencesDataSource: NiaPreferencesDataSource,
) : UserDataRepository {
@ -43,7 +43,7 @@ internal class FakeUserDataRepository @Inject constructor(
override suspend fun setTopicIdFollowed(followedTopicId: String, followed: Boolean) =
niaPreferencesDataSource.setTopicIdFollowed(followedTopicId, followed)
override suspend fun updateNewsResourceBookmark(newsResourceId: String, bookmarked: Boolean) {
override suspend fun setNewsResourceBookmarked(newsResourceId: String, bookmarked: Boolean) {
niaPreferencesDataSource.setNewsResourceBookmarked(newsResourceId, bookmarked)
}

@ -1,3 +1,3 @@
# :core:data module
![Dependency graph](../../docs/images/graphs/dep_graph_core_data.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_data.svg)

@ -19,8 +19,6 @@ package com.google.samples.apps.nowinandroid.core.data
import android.util.Log
import com.google.samples.apps.nowinandroid.core.datastore.ChangeListVersions
import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.combine
import kotlin.coroutines.cancellation.CancellationException
/**
@ -104,29 +102,3 @@ suspend fun Synchronizer.changeListSync(
versionUpdater(latestVersion)
}
}.isSuccess
/**
* Returns a [Flow] whose values are generated by [transform] function that process the most
* recently emitted values by each flow.
*/
fun <T1, T2, T3, T4, T5, T6, R> combine(
flow: Flow<T1>,
flow2: Flow<T2>,
flow3: Flow<T3>,
flow4: Flow<T4>,
flow5: Flow<T5>,
flow6: Flow<T6>,
transform: suspend (T1, T2, T3, T4, T5, T6) -> R,
): Flow<R> = combine(
combine(flow, flow2, flow3, ::Triple),
combine(flow4, flow5, flow6, ::Triple),
) { t1, t2 ->
transform(
t1.first,
t1.second,
t1.third,
t2.first,
t2.second,
t2.third,
)
}

@ -42,7 +42,7 @@ internal class OfflineFirstUserDataRepository @Inject constructor(
analyticsHelper.logTopicFollowToggled(followedTopicId, followed)
}
override suspend fun updateNewsResourceBookmark(newsResourceId: String, bookmarked: Boolean) {
override suspend fun setNewsResourceBookmarked(newsResourceId: String, bookmarked: Boolean) {
niaPreferencesDataSource.setNewsResourceBookmarked(newsResourceId, bookmarked)
analyticsHelper.logNewsResourceBookmarkToggled(
newsResourceId = newsResourceId,

@ -41,7 +41,7 @@ interface UserDataRepository {
/**
* Updates the bookmarked status for a news resource
*/
suspend fun updateNewsResourceBookmark(newsResourceId: String, bookmarked: Boolean)
suspend fun setNewsResourceBookmarked(newsResourceId: String, bookmarked: Boolean)
/**
* Updates the viewed status for a news resource

@ -133,7 +133,7 @@ class OfflineFirstUserDataRepositoryTest {
@Test
fun offlineFirstUserDataRepository_bookmark_news_resource_logic_delegates_to_nia_preferences() =
testScope.runTest {
subject.updateNewsResourceBookmark(newsResourceId = "0", bookmarked = true)
subject.setNewsResourceBookmarked(newsResourceId = "0", bookmarked = true)
assertEquals(
setOf("0"),
@ -142,7 +142,7 @@ class OfflineFirstUserDataRepositoryTest {
.first(),
)
subject.updateNewsResourceBookmark(newsResourceId = "1", bookmarked = true)
subject.setNewsResourceBookmarked(newsResourceId = "1", bookmarked = true)
assertEquals(
setOf("0", "1"),

@ -17,7 +17,7 @@
package com.google.samples.apps.nowinandroid.core.data.testdoubles
import com.google.samples.apps.nowinandroid.core.network.NiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.network.fake.FakeNiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.network.demo.DemoNiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.network.model.NetworkChangeList
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource
import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic
@ -35,7 +35,7 @@ enum class CollectionType {
*/
class TestNiaNetworkDataSource : NiaNetworkDataSource {
private val source = FakeNiaNetworkDataSource(
private val source = DemoNiaNetworkDataSource(
UnconfinedTestDispatcher(),
Json { ignoreUnknownKeys = true },
)

@ -1,3 +1,3 @@
# :core:database module
![Dependency graph](../../docs/images/graphs/dep_graph_core_database.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_database.svg)

@ -1,5 +1,5 @@
/*
* Copyright 2022 The Android Open Source Project
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,8 +14,9 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.database
package com.google.samples.apps.nowinandroid.core.database.di
import com.google.samples.apps.nowinandroid.core.database.NiaDatabase
import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceDao
import com.google.samples.apps.nowinandroid.core.database.dao.NewsResourceFtsDao
import com.google.samples.apps.nowinandroid.core.database.dao.RecentSearchQueryDao

@ -1,5 +1,5 @@
/*
* Copyright 2022 The Android Open Source Project
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,10 +14,11 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.database
package com.google.samples.apps.nowinandroid.core.database.di
import android.content.Context
import androidx.room.Room
import com.google.samples.apps.nowinandroid.core.database.NiaDatabase
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn

@ -0,0 +1,3 @@
# :core:datastore-proto module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore_proto.svg)

@ -1,3 +1,3 @@
# :core:datastore-test module
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore_test.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore_test.svg)

@ -1,3 +1,3 @@
# :core:datastore module
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_datastore.svg)

@ -1,3 +1,3 @@
# :core:designsystem module
![Dependency graph](../../docs/images/graphs/dep_graph_core_designsystem.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_designsystem.svg)

@ -18,7 +18,6 @@ package com.google.samples.apps.nowinandroid.core.designsystem.component
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
@ -43,7 +42,6 @@ import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
* @param label The text label content.
*/
@Composable
@OptIn(ExperimentalMaterial3Api::class)
fun NiaFilterChip(
selected: Boolean,
onSelectedChange: (Boolean) -> Unit,

@ -45,7 +45,7 @@ import org.robolectric.annotation.LooperMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(application = HiltTestApplication::class, qualifiers = "480dpi")
@LooperMode(LooperMode.Mode.PAUSED)
class FilterChipScreenshotTests() {
class FilterChipScreenshotTests {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()

@ -39,7 +39,7 @@ import org.robolectric.annotation.LooperMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(application = HiltTestApplication::class, qualifiers = "480dpi")
@LooperMode(LooperMode.Mode.PAUSED)
class LoadingWheelScreenshotTests() {
class LoadingWheelScreenshotTests {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()

@ -46,7 +46,7 @@ import org.robolectric.annotation.LooperMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(application = HiltTestApplication::class, qualifiers = "480dpi")
@LooperMode(LooperMode.Mode.PAUSED)
class NavigationScreenshotTests() {
class NavigationScreenshotTests {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()

@ -44,7 +44,7 @@ import org.robolectric.annotation.LooperMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(application = HiltTestApplication::class, qualifiers = "480dpi")
@LooperMode(LooperMode.Mode.PAUSED)
class TabsScreenshotTests() {
class TabsScreenshotTests {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()

@ -41,7 +41,7 @@ import org.robolectric.annotation.LooperMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(application = HiltTestApplication::class, qualifiers = "480dpi")
@LooperMode(LooperMode.Mode.PAUSED)
class TagScreenshotTests() {
class TagScreenshotTests {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()

@ -45,7 +45,7 @@ import org.robolectric.annotation.LooperMode
@GraphicsMode(GraphicsMode.Mode.NATIVE)
@Config(application = HiltTestApplication::class, qualifiers = "480dpi")
@LooperMode(LooperMode.Mode.PAUSED)
class TopAppBarScreenshotTests() {
class TopAppBarScreenshotTests {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()

@ -0,0 +1,3 @@
# :core:domain module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_domain.svg)

@ -1,31 +0,0 @@
/*
* Copyright 2023 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.domain
import com.google.samples.apps.nowinandroid.core.data.repository.SearchContentsRepository
import kotlinx.coroutines.flow.Flow
import javax.inject.Inject
/**
* A use case which returns total count of *Fts tables
*/
class GetSearchContentsCountUseCase @Inject constructor(
private val searchContentsRepository: SearchContentsRepository,
) {
operator fun invoke(): Flow<Int> =
searchContentsRepository.getSearchContentsCount()
}

@ -1,3 +1,3 @@
# :core:model module
![Dependency graph](../../docs/images/graphs/dep_graph_core_model.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_model.svg)

@ -1,3 +1,3 @@
# :core:network module
![Dependency graph](../../docs/images/graphs/dep_graph_core_network.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_network.svg)

@ -20,6 +20,6 @@
java.lang.IllegalStateException: () -> kotlin.String at org.jetbrains.kotlin.asJava.classes.KtLightClassForFacadeImpl$Companion.createForFacadeNoCache
-->
<issue id="LintError">
<ignore path="**/JvmUnitTestFakeAssetManager.kt" />
<ignore path="**/JvmUnitTestDemoAssetManager.kt" />
</issue>
</lint>

@ -17,7 +17,7 @@
package com.google.samples.apps.nowinandroid.core.network.di
import com.google.samples.apps.nowinandroid.core.network.NiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.network.fake.FakeNiaNetworkDataSource
import com.google.samples.apps.nowinandroid.core.network.demo.DemoNiaNetworkDataSource
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
@ -28,5 +28,5 @@ import dagger.hilt.components.SingletonComponent
internal interface FlavoredNetworkModule {
@Binds
fun binds(impl: FakeNiaNetworkDataSource): NiaNetworkDataSource
fun binds(impl: DemoNiaNetworkDataSource): NiaNetworkDataSource
}

@ -14,8 +14,7 @@
* limitations under the License.
*/
import androidx.annotation.VisibleForTesting
import com.google.samples.apps.nowinandroid.core.network.fake.FakeAssetManager
import com.google.samples.apps.nowinandroid.core.network.demo.DemoAssetManager
import java.io.File
import java.io.InputStream
import java.util.Properties
@ -25,8 +24,8 @@ import java.util.Properties
* It must remain on the root package for an easier [Class.getResource] with relative paths.
* @see <a href="https://developer.android.com/reference/tools/gradle-api/7.3/com/android/build/api/dsl/UnitTestOptions">UnitTestOptions</a>
*/
@VisibleForTesting
internal object JvmUnitTestFakeAssetManager : FakeAssetManager {
internal object JvmUnitTestDemoAssetManager : DemoAssetManager {
private val config =
requireNotNull(javaClass.getResource("com/android/tools/test_config.properties")) {
"""

@ -1,5 +1,5 @@
/*
* Copyright 2022 The Android Open Source Project
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,10 +14,10 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.network.fake
package com.google.samples.apps.nowinandroid.core.network.demo
import java.io.InputStream
fun interface FakeAssetManager {
fun interface DemoAssetManager {
fun open(fileName: String): InputStream
}

@ -1,5 +1,5 @@
/*
* Copyright 2022 The Android Open Source Project
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,9 +14,9 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.network.fake
package com.google.samples.apps.nowinandroid.core.network.demo
import JvmUnitTestFakeAssetManager
import JvmUnitTestDemoAssetManager
import com.google.samples.apps.nowinandroid.core.network.Dispatcher
import com.google.samples.apps.nowinandroid.core.network.NiaDispatchers.IO
import com.google.samples.apps.nowinandroid.core.network.NiaNetworkDataSource
@ -33,10 +33,10 @@ import javax.inject.Inject
/**
* [NiaNetworkDataSource] implementation that provides static news resources to aid development
*/
class FakeNiaNetworkDataSource @Inject constructor(
class DemoNiaNetworkDataSource @Inject constructor(
@Dispatcher(IO) private val ioDispatcher: CoroutineDispatcher,
private val networkJson: Json,
private val assets: FakeAssetManager = JvmUnitTestFakeAssetManager,
private val assets: DemoAssetManager = JvmUnitTestDemoAssetManager,
) : NiaNetworkDataSource {
@OptIn(ExperimentalSerializationApi::class)

@ -22,7 +22,7 @@ import coil.ImageLoader
import coil.decode.SvgDecoder
import coil.util.DebugLogger
import com.google.samples.apps.nowinandroid.core.network.BuildConfig
import com.google.samples.apps.nowinandroid.core.network.fake.FakeAssetManager
import com.google.samples.apps.nowinandroid.core.network.demo.DemoAssetManager
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
@ -46,9 +46,9 @@ internal object NetworkModule {
@Provides
@Singleton
fun providesFakeAssetManager(
fun providesDemoAssetManager(
@ApplicationContext context: Context,
): FakeAssetManager = FakeAssetManager(context.assets::open)
): DemoAssetManager = DemoAssetManager(context.assets::open)
@Provides
@Singleton

@ -1,5 +1,5 @@
/*
* Copyright 2022 The Android Open Source Project
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,9 +14,9 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.core.network.fake
package com.google.samples.apps.nowinandroid.core.network.demo
import JvmUnitTestFakeAssetManager
import JvmUnitTestDemoAssetManager
import com.google.samples.apps.nowinandroid.core.network.model.NetworkNewsResource
import com.google.samples.apps.nowinandroid.core.network.model.NetworkTopic
import kotlinx.coroutines.test.StandardTestDispatcher
@ -29,18 +29,18 @@ import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class FakeNiaNetworkDataSourceTest {
class DemoNiaNetworkDataSourceTest {
private lateinit var subject: FakeNiaNetworkDataSource
private lateinit var subject: DemoNiaNetworkDataSource
private val testDispatcher = StandardTestDispatcher()
@Before
fun setUp() {
subject = FakeNiaNetworkDataSource(
subject = DemoNiaNetworkDataSource(
ioDispatcher = testDispatcher,
networkJson = Json { ignoreUnknownKeys = true },
assets = JvmUnitTestFakeAssetManager,
assets = JvmUnitTestDemoAssetManager,
)
}

@ -0,0 +1,3 @@
# :core:notifications module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_notifications.svg)

@ -0,0 +1,3 @@
# :core:screenshot-testing module
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_screenshot_testing.svg)

@ -1,3 +1,3 @@
# :core:testing module
![Dependency graph](../../docs/images/graphs/dep_graph_core_testing.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_testing.svg)

@ -61,7 +61,7 @@ class TestUserDataRepository : UserDataRepository {
}
}
override suspend fun updateNewsResourceBookmark(newsResourceId: String, bookmarked: Boolean) {
override suspend fun setNewsResourceBookmarked(newsResourceId: String, bookmarked: Boolean) {
currentUserData.let { current ->
val bookmarkedNews = if (bookmarked) {
current.bookmarkedNewsResources + newsResourceId

@ -1,3 +1,3 @@
# :core:ui module
![Dependency graph](../../docs/images/graphs/dep_graph_core_ui.png)
## Dependency graph
![Dependency graph](../../docs/images/graphs/dep_graph_core_ui.svg)

@ -1,5 +1,5 @@
/*
* Copyright 2022 The Android Open Source Project
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -14,7 +14,7 @@
* limitations under the License.
*/
package com.google.samples.apps.nowinandroid.feature.interests
package com.google.samples.apps.nowinandroid.core.ui
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
@ -38,7 +38,7 @@ import com.google.samples.apps.nowinandroid.core.designsystem.component.DynamicA
import com.google.samples.apps.nowinandroid.core.designsystem.component.NiaIconToggleButton
import com.google.samples.apps.nowinandroid.core.designsystem.icon.NiaIcons
import com.google.samples.apps.nowinandroid.core.designsystem.theme.NiaTheme
import com.google.samples.apps.nowinandroid.feature.interests.R.string
import com.google.samples.apps.nowinandroid.core.ui.R.string
@Composable
fun InterestsItem(
@ -70,7 +70,7 @@ fun InterestsItem(
Icon(
imageVector = NiaIcons.Add,
contentDescription = stringResource(
id = string.feature_interests_card_follow_button_content_desc,
id = string.core_ui_interests_card_follow_button_content_desc,
),
)
},
@ -78,7 +78,7 @@ fun InterestsItem(
Icon(
imageVector = NiaIcons.Check,
contentDescription = stringResource(
id = string.feature_interests_card_unfollow_button_content_desc,
id = string.core_ui_interests_card_unfollow_button_content_desc,
),
)
},

@ -33,7 +33,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
@ -78,7 +77,6 @@ import java.util.Locale
* [NewsResource] card used on the following screens: For You, Saved
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun NewsResourceCardExpanded(
userNewsResource: UserNewsResource,

@ -26,4 +26,7 @@
<string name="core_ui_topic_chip_content_description_when_followed">%1$s is followed</string>
<string name="core_ui_topic_chip_content_description_when_not_followed">%1$s is not followed</string>
<string name="core_ui_interests_card_follow_button_content_desc">Follow interest</string>
<string name="core_ui_interests_card_unfollow_button_content_desc">Unfollow interest</string>
</resources>

@ -25,6 +25,8 @@ The app architecture has three layers: a [data layer](https://developer.android.
<img src="images/architecture-1-overall.png" width="600px" alt="Diagram showing overall app architecture" />
</center>
> [!NOTE]
> The official Android architecture is different from other architectures, such as "Clean Architecture". Concepts from other architectures may not apply here, or be applied in different ways. [More discussion here](https://github.com/android/nowinandroid/discussions/1273).
The architecture follows a reactive programming model with [unidirectional data flow](https://developer.android.com/jetpack/guide/ui-layer#udf). With the data layer at the bottom, the key concepts are:

Binary file not shown.

Before

Width:  |  Height:  |  Size: 48 KiB

@ -0,0 +1,463 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="1178pt" height="404pt"
viewBox="0.00 0.00 1178.13 404.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 400)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-400 1174.13,-400 1174.13,4 -4,4"/>
<!-- :app -->
<g id="node1" class="node">
<title>:app</title>
<ellipse fill="none" stroke="black" cx="626.13" cy="-378" rx="27" ry="18"/>
<text text-anchor="middle" x="626.13" y="-372.95" font-family="Times,serif" font-size="14.00">:app</text>
</g>
<!-- :feature:interests -->
<g id="node2" class="node">
<title>:feature:interests</title>
<ellipse fill="none" stroke="black" cx="289.13" cy="-306" rx="72.34" ry="18"/>
<text text-anchor="middle" x="289.13" y="-300.95" font-family="Times,serif" font-size="14.00">:feature:interests</text>
</g>
<!-- :app&#45;&gt;:feature:interests -->
<g id="edge1" class="edge">
<title>:app&#45;&gt;:feature:interests</title>
<path fill="none" stroke="red" stroke-width="2" d="M600.52,-371.68C548.58,-360.89 429.43,-336.14 354.49,-320.58"/>
<polygon fill="red" stroke="red" stroke-width="2" points="356.78,-317.48 346.28,-318.87 355.36,-324.33 356.78,-317.48"/>
</g>
<!-- :feature:foryou -->
<g id="node3" class="node">
<title>:feature:foryou</title>
<ellipse fill="none" stroke="black" cx="132.13" cy="-306" rx="66.19" ry="18"/>
<text text-anchor="middle" x="132.13" y="-300.95" font-family="Times,serif" font-size="14.00">:feature:foryou</text>
</g>
<!-- :app&#45;&gt;:feature:foryou -->
<g id="edge2" class="edge">
<title>:app&#45;&gt;:feature:foryou</title>
<path fill="none" stroke="black" d="M599.59,-374.51C533.28,-368.1 355.12,-349.7 208.13,-324 202.83,-323.07 197.34,-322.02 191.85,-320.91"/>
<polygon fill="black" stroke="black" points="192.63,-317.5 182.12,-318.88 191.19,-324.35 192.63,-317.5"/>
</g>
<!-- :feature:bookmarks -->
<g id="node4" class="node">
<title>:feature:bookmarks</title>
<ellipse fill="none" stroke="black" cx="787.13" cy="-306" rx="83.08" ry="18"/>
<text text-anchor="middle" x="787.13" y="-300.95" font-family="Times,serif" font-size="14.00">:feature:bookmarks</text>
</g>
<!-- :app&#45;&gt;:feature:bookmarks -->
<g id="edge3" class="edge">
<title>:app&#45;&gt;:feature:bookmarks</title>
<path fill="none" stroke="black" d="M648.51,-367.27C672.32,-356.92 710.64,-340.25 740.97,-327.07"/>
<polygon fill="black" stroke="black" points="742.16,-330.37 749.93,-323.17 739.37,-323.95 742.16,-330.37"/>
</g>
<!-- :feature:topic -->
<g id="node5" class="node">
<title>:feature:topic</title>
<ellipse fill="none" stroke="black" cx="626.13" cy="-306" rx="60.05" ry="18"/>
<text text-anchor="middle" x="626.13" y="-300.95" font-family="Times,serif" font-size="14.00">:feature:topic</text>
</g>
<!-- :app&#45;&gt;:feature:topic -->
<g id="edge4" class="edge">
<title>:app&#45;&gt;:feature:topic</title>
<path fill="none" stroke="black" d="M626.13,-359.7C626.13,-352.41 626.13,-343.73 626.13,-335.54"/>
<polygon fill="black" stroke="black" points="629.63,-335.62 626.13,-325.62 622.63,-335.62 629.63,-335.62"/>
</g>
<!-- :feature:search -->
<g id="node6" class="node">
<title>:feature:search</title>
<ellipse fill="none" stroke="black" cx="445.13" cy="-306" rx="65.17" ry="18"/>
<text text-anchor="middle" x="445.13" y="-300.95" font-family="Times,serif" font-size="14.00">:feature:search</text>
</g>
<!-- :app&#45;&gt;:feature:search -->
<g id="edge5" class="edge">
<title>:app&#45;&gt;:feature:search</title>
<path fill="none" stroke="black" d="M603.2,-368.13C575.53,-357.43 528.27,-339.16 492.62,-325.37"/>
<polygon fill="black" stroke="black" points="493.96,-322.13 483.37,-321.79 491.44,-328.66 493.96,-322.13"/>
</g>
<!-- :feature:settings -->
<g id="node7" class="node">
<title>:feature:settings</title>
<ellipse fill="none" stroke="black" cx="958.13" cy="-306" rx="69.78" ry="18"/>
<text text-anchor="middle" x="958.13" y="-300.95" font-family="Times,serif" font-size="14.00">:feature:settings</text>
</g>
<!-- :app&#45;&gt;:feature:settings -->
<g id="edge6" class="edge">
<title>:app&#45;&gt;:feature:settings</title>
<path fill="none" stroke="black" d="M651.63,-371.7C697.28,-362.22 795.94,-341.66 879.13,-324 884.26,-322.91 889.58,-321.78 894.92,-320.63"/>
<polygon fill="black" stroke="black" points="895.34,-324.12 904.39,-318.6 893.88,-317.28 895.34,-324.12"/>
</g>
<!-- :core:common -->
<g id="node8" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="760.13" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="760.13" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<!-- :app&#45;&gt;:core:common -->
<g id="edge7" class="edge">
<title>:app&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M653.52,-377.16C744.97,-377.21 1038.43,-373.43 1116.13,-324 1155.16,-299.17 1170.13,-281.27 1170.13,-235 1170.13,-235 1170.13,-235 1170.13,-161 1170.13,-114.96 1155.63,-97.23 1117.13,-72 1064.1,-37.25 896.54,-47.11 834.13,-36 829.18,-35.12 824.06,-34.12 818.93,-33.06"/>
<polygon fill="black" stroke="black" points="819.74,-29.66 809.23,-30.99 818.28,-36.51 819.74,-29.66"/>
</g>
<!-- :core:ui -->
<g id="node9" class="node">
<title>:core:ui</title>
<ellipse fill="none" stroke="black" cx="530.13" cy="-234" rx="39.07" ry="18"/>
<text text-anchor="middle" x="530.13" y="-228.95" font-family="Times,serif" font-size="14.00">:core:ui</text>
</g>
<!-- :app&#45;&gt;:core:ui -->
<g id="edge8" class="edge">
<title>:app&#45;&gt;:core:ui</title>
<path fill="none" stroke="black" d="M605.49,-365.91C590.07,-356.62 569.59,-342.01 557.13,-324 544.69,-306.03 537.86,-282.18 534.18,-263.69"/>
<polygon fill="black" stroke="black" points="537.64,-263.17 532.45,-253.93 530.75,-264.38 537.64,-263.17"/>
</g>
<!-- :core:designsystem -->
<g id="node10" class="node">
<title>:core:designsystem</title>
<ellipse fill="none" stroke="black" cx="749.13" cy="-162" rx="82.06" ry="18"/>
<text text-anchor="middle" x="749.13" y="-156.95" font-family="Times,serif" font-size="14.00">:core:designsystem</text>
</g>
<!-- :app&#45;&gt;:core:designsystem -->
<g id="edge9" class="edge">
<title>:app&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M653.33,-375.54C740.95,-370.56 1011.68,-352.94 1037.13,-324 1047.69,-311.98 1045.9,-301.38 1037.13,-288 1023.35,-267 880.1,-211.43 800.97,-181.97"/>
<polygon fill="black" stroke="black" points="802.26,-178.71 791.67,-178.52 799.82,-185.28 802.26,-178.71"/>
</g>
<!-- :core:data -->
<g id="node11" class="node">
<title>:core:data</title>
<ellipse fill="none" stroke="black" cx="564.13" cy="-162" rx="47.26" ry="18"/>
<text text-anchor="middle" x="564.13" y="-156.95" font-family="Times,serif" font-size="14.00">:core:data</text>
</g>
<!-- :app&#45;&gt;:core:data -->
<g id="edge10" class="edge">
<title>:app&#45;&gt;:core:data</title>
<path fill="none" stroke="black" d="M598.83,-376.56C490.37,-374.55 94,-364.47 57.13,-324 -72.66,-181.56 345.93,-164.36 505.2,-162.87"/>
<polygon fill="black" stroke="black" points="505.17,-166.37 515.14,-162.79 505.12,-159.37 505.17,-166.37"/>
</g>
<!-- :core:model -->
<g id="node12" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="391.13" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="391.13" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :app&#45;&gt;:core:model -->
<g id="edge11" class="edge">
<title>:app&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M599.09,-376.3C488.77,-373.19 76.42,-359.31 31.13,-324 -1.91,-298.24 0.13,-276.89 0.13,-235 0.13,-235 0.13,-235 0.13,-161 0.13,-91.6 220.52,-46.37 330.37,-28.13"/>
<polygon fill="black" stroke="black" points="330.81,-31.61 340.11,-26.54 329.68,-24.7 330.81,-31.61"/>
</g>
<!-- :core:analytics -->
<g id="node13" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="1043.13" cy="-90" rx="64.66" ry="18"/>
<text text-anchor="middle" x="1043.13" y="-84.95" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
<!-- :app&#45;&gt;:core:analytics -->
<g id="edge12" class="edge">
<title>:app&#45;&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M653.6,-377.55C742.51,-378.76 1020.63,-378.07 1088.13,-324 1126.58,-293.2 1125.57,-263.67 1113.13,-216 1103.47,-178.98 1080.15,-141.32 1063.07,-117.16"/>
<polygon fill="black" stroke="black" points="1066,-115.25 1057.3,-109.2 1060.33,-119.35 1066,-115.25"/>
</g>
<!-- :sync:work -->
<g id="node14" class="node">
<title>:sync:work</title>
<ellipse fill="none" stroke="black" cx="1052.13" cy="-234" rx="51.86" ry="18"/>
<text text-anchor="middle" x="1052.13" y="-228.95" font-family="Times,serif" font-size="14.00">:sync:work</text>
</g>
<!-- :app&#45;&gt;:sync:work -->
<g id="edge13" class="edge">
<title>:app&#45;&gt;:sync:work</title>
<path fill="none" stroke="black" d="M653.22,-375.82C745.34,-371.53 1042.3,-355.35 1070.13,-324 1085.05,-307.19 1077.76,-281.49 1068.38,-262.03"/>
<polygon fill="black" stroke="black" points="1071.54,-260.51 1063.78,-253.29 1065.34,-263.77 1071.54,-260.51"/>
</g>
<!-- :feature:interests&#45;&gt;:core:ui -->
<g id="edge14" class="edge">
<title>:feature:interests&#45;&gt;:core:ui</title>
<path fill="none" stroke="black" d="M335.02,-291.67C378.65,-279 443.79,-260.08 486.61,-247.64"/>
<polygon fill="black" stroke="black" points="487.45,-251.04 496.07,-244.89 485.49,-244.32 487.45,-251.04"/>
</g>
<!-- :feature:interests&#45;&gt;:core:designsystem -->
<g id="edge15" class="edge">
<title>:feature:interests&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M320.56,-289.4C366.58,-266.76 450.28,-226.34 482.13,-216 543.5,-196.07 615.36,-182.32 669.06,-173.83"/>
<polygon fill="black" stroke="black" points="669.39,-177.32 678.73,-172.33 668.32,-170.41 669.39,-177.32"/>
</g>
<!-- :feature:interests&#45;&gt;:core:data -->
<g id="edge16" class="edge">
<title>:feature:interests&#45;&gt;:core:data</title>
<path fill="none" stroke="black" d="M308.22,-288.26C331.27,-268.74 371.86,-236.53 411.13,-216 444.18,-198.71 484.08,-185.11 514.85,-176.01"/>
<polygon fill="black" stroke="black" points="515.65,-179.42 524.28,-173.28 513.7,-172.7 515.65,-179.42"/>
</g>
<!-- :core:domain -->
<g id="node15" class="node">
<title>:core:domain</title>
<ellipse fill="none" stroke="black" cx="261.13" cy="-234" rx="60.05" ry="18"/>
<text text-anchor="middle" x="261.13" y="-228.95" font-family="Times,serif" font-size="14.00">:core:domain</text>
</g>
<!-- :feature:interests&#45;&gt;:core:domain -->
<g id="edge17" class="edge">
<title>:feature:interests&#45;&gt;:core:domain</title>
<path fill="none" stroke="red" stroke-width="2" d="M282.21,-287.7C279.19,-280.15 275.57,-271.12 272.2,-262.68"/>
<polygon fill="red" stroke="red" stroke-width="2" points="276.06,-262.9 269.09,-254.91 269.56,-265.5 276.06,-262.9"/>
</g>
<!-- :feature:foryou&#45;&gt;:core:ui -->
<g id="edge37" class="edge">
<title>:feature:foryou&#45;&gt;:core:ui</title>
<path fill="none" stroke="black" d="M181.1,-293.62C190.07,-291.65 199.37,-289.7 208.13,-288 304.99,-269.21 419.09,-251.46 482.14,-242.03"/>
<polygon fill="black" stroke="black" points="482.43,-245.52 491.81,-240.59 481.4,-238.6 482.43,-245.52"/>
</g>
<!-- :feature:foryou&#45;&gt;:core:designsystem -->
<g id="edge38" class="edge">
<title>:feature:foryou&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M180.42,-293.47C229.84,-281.43 303.12,-262.8 330.13,-252 361.15,-239.59 364.45,-226.64 396.13,-216 421.45,-207.49 572.8,-186.4 668.92,-173.54"/>
<polygon fill="black" stroke="black" points="669.12,-177.04 678.56,-172.25 668.19,-170.1 669.12,-177.04"/>
</g>
<!-- :feature:foryou&#45;&gt;:core:data -->
<g id="edge39" class="edge">
<title>:feature:foryou&#45;&gt;:core:data</title>
<path fill="none" stroke="black" d="M138.65,-287.67C147.42,-266.94 165.12,-232.91 192.13,-216 243.29,-183.95 414.92,-170.54 506.08,-165.57"/>
<polygon fill="black" stroke="black" points="506.01,-169.08 515.81,-165.06 505.64,-162.09 506.01,-169.08"/>
</g>
<!-- :feature:foryou&#45;&gt;:core:domain -->
<g id="edge40" class="edge">
<title>:feature:foryou&#45;&gt;:core:domain</title>
<path fill="none" stroke="black" d="M160.76,-289.46C179.09,-279.52 203.09,-266.5 223.02,-255.68"/>
<polygon fill="black" stroke="black" points="224.57,-258.82 231.69,-250.97 221.23,-252.67 224.57,-258.82"/>
</g>
<!-- :feature:bookmarks&#45;&gt;:core:ui -->
<g id="edge41" class="edge">
<title>:feature:bookmarks&#45;&gt;:core:ui</title>
<path fill="none" stroke="black" d="M736.66,-291.25C689.36,-278.37 619.35,-259.3 574.23,-247.01"/>
<polygon fill="black" stroke="black" points="575.36,-243.69 564.79,-244.44 573.52,-250.45 575.36,-243.69"/>
</g>
<!-- :feature:bookmarks&#45;&gt;:core:designsystem -->
<g id="edge42" class="edge">
<title>:feature:bookmarks&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M782.47,-287.59C776.02,-263.5 764.31,-219.75 756.63,-191.03"/>
<polygon fill="black" stroke="black" points="760.06,-190.33 754.1,-181.57 753.3,-192.14 760.06,-190.33"/>
</g>
<!-- :feature:bookmarks&#45;&gt;:core:data -->
<g id="edge43" class="edge">
<title>:feature:bookmarks&#45;&gt;:core:data</title>
<path fill="none" stroke="black" d="M758.5,-288.7C740.69,-278.43 717.39,-264.73 697.13,-252 661.72,-229.75 621.97,-202.9 595.23,-184.55"/>
<polygon fill="black" stroke="black" points="597.46,-181.84 587.24,-179.05 593.49,-187.6 597.46,-181.84"/>
</g>
<!-- :feature:topic&#45;&gt;:core:ui -->
<g id="edge44" class="edge">
<title>:feature:topic&#45;&gt;:core:ui</title>
<path fill="none" stroke="black" d="M604.34,-289.12C591,-279.39 573.76,-266.82 559.26,-256.24"/>
<polygon fill="black" stroke="black" points="561.55,-253.58 551.41,-250.52 557.43,-259.24 561.55,-253.58"/>
</g>
<!-- :feature:topic&#45;&gt;:core:designsystem -->
<g id="edge45" class="edge">
<title>:feature:topic&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M640.72,-288.15C662.17,-263.39 702.37,-216.97 727.33,-188.16"/>
<polygon fill="black" stroke="black" points="729.66,-190.82 733.56,-180.97 724.37,-186.24 729.66,-190.82"/>
</g>
<!-- :feature:topic&#45;&gt;:core:data -->
<g id="edge46" class="edge">
<title>:feature:topic&#45;&gt;:core:data</title>
<path fill="none" stroke="black" d="M618.65,-287.87C608.08,-263.67 588.67,-219.21 576.09,-190.39"/>
<polygon fill="black" stroke="black" points="579.45,-189.34 572.24,-181.58 573.03,-192.14 579.45,-189.34"/>
</g>
<!-- :feature:search&#45;&gt;:core:ui -->
<g id="edge47" class="edge">
<title>:feature:search&#45;&gt;:core:ui</title>
<path fill="none" stroke="black" d="M465.27,-288.41C476.46,-279.2 490.54,-267.6 502.72,-257.57"/>
<polygon fill="black" stroke="black" points="504.92,-260.29 510.41,-251.24 500.47,-254.89 504.92,-260.29"/>
</g>
<!-- :feature:search&#45;&gt;:core:designsystem -->
<g id="edge48" class="edge">
<title>:feature:search&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M483.1,-291.03C509.94,-280.88 546.6,-266.44 578.13,-252 624.31,-230.84 675.85,-203.48 710.15,-184.72"/>
<polygon fill="black" stroke="black" points="711.82,-187.8 718.89,-179.91 708.44,-181.66 711.82,-187.8"/>
</g>
<!-- :feature:search&#45;&gt;:core:data -->
<g id="edge49" class="edge">
<title>:feature:search&#45;&gt;:core:data</title>
<path fill="none" stroke="black" d="M449.03,-287.64C454.05,-268.42 464.33,-237.33 482.13,-216 493.69,-202.15 509.81,-190.65 524.7,-181.95"/>
<polygon fill="black" stroke="black" points="526.38,-185.02 533.42,-177.11 522.98,-178.9 526.38,-185.02"/>
</g>
<!-- :feature:search&#45;&gt;:core:domain -->
<g id="edge50" class="edge">
<title>:feature:search&#45;&gt;:core:domain</title>
<path fill="none" stroke="black" d="M407.89,-290.83C379.05,-279.86 338.93,-264.6 307.92,-252.8"/>
<polygon fill="black" stroke="black" points="309.37,-249.61 298.78,-249.32 306.88,-256.15 309.37,-249.61"/>
</g>
<!-- :feature:settings&#45;&gt;:core:ui -->
<g id="edge51" class="edge">
<title>:feature:settings&#45;&gt;:core:ui</title>
<path fill="none" stroke="black" d="M906.8,-293.48C897.6,-291.55 888.1,-289.64 879.13,-288 772.28,-268.41 646.09,-250.54 578.56,-241.4"/>
<polygon fill="black" stroke="black" points="579.32,-237.97 568.94,-240.1 578.38,-244.91 579.32,-237.97"/>
</g>
<!-- :feature:settings&#45;&gt;:core:designsystem -->
<g id="edge52" class="edge">
<title>:feature:settings&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M934.15,-288.71C896.95,-263.43 825.2,-214.68 782.81,-185.88"/>
<polygon fill="black" stroke="black" points="784.96,-183.11 774.72,-180.39 781.02,-188.9 784.96,-183.11"/>
</g>
<!-- :feature:settings&#45;&gt;:core:data -->
<g id="edge53" class="edge">
<title>:feature:settings&#45;&gt;:core:data</title>
<path fill="none" stroke="black" d="M918.54,-290.73C844.61,-264.09 686.03,-206.93 608.56,-179.01"/>
<polygon fill="black" stroke="black" points="609.75,-175.72 599.16,-175.63 607.38,-182.31 609.75,-175.72"/>
</g>
<!-- :core:ui&#45;&gt;:core:designsystem -->
<g id="edge19" class="edge">
<title>:core:ui&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M561.51,-222.93C568.62,-220.65 576.13,-218.24 583.13,-216 619.89,-204.24 661.23,-191.03 693.47,-180.75"/>
<polygon fill="black" stroke="black" points="694.13,-184.21 702.59,-177.84 692,-177.55 694.13,-184.21"/>
</g>
<!-- :core:ui&#45;&gt;:core:model -->
<g id="edge20" class="edge">
<title>:core:ui&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M496.49,-224.51C447.08,-210.02 356.59,-175.34 318.13,-108 310.19,-94.11 310.86,-86.25 318.13,-72 325.1,-58.32 337.43,-47.35 350,-39.02"/>
<polygon fill="black" stroke="black" points="351.58,-42.15 358.28,-33.95 347.93,-36.18 351.58,-42.15"/>
</g>
<!-- :core:ui&#45;&gt;:core:analytics -->
<g id="edge18" class="edge">
<title>:core:ui&#45;&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M555.54,-220.04C574.11,-210.1 599.56,-195.49 620.13,-180 638.71,-166.01 637.05,-153.85 658.13,-144 782.97,-85.63 830.22,-130.89 966.13,-108 972.03,-107.01 978.17,-105.85 984.28,-104.63"/>
<polygon fill="black" stroke="black" points="984.65,-108.13 993.73,-102.67 983.23,-101.27 984.65,-108.13"/>
</g>
<!-- :core:data&#45;&gt;:core:common -->
<g id="edge21" class="edge">
<title>:core:data&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M601.15,-150.4C609.05,-148.22 617.35,-145.99 625.13,-144 693.65,-126.5 736.14,-163.38 780.13,-108 794.09,-90.42 786.46,-65.02 776.87,-45.85"/>
<polygon fill="black" stroke="black" points="780.04,-44.37 772.18,-37.26 773.9,-47.72 780.04,-44.37"/>
</g>
<!-- :core:data&#45;&gt;:core:analytics -->
<g id="edge25" class="edge">
<title>:core:data&#45;&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M600.02,-149.93C608.21,-147.7 616.92,-145.57 625.13,-144 774.83,-115.44 815.71,-132.47 966.13,-108 972.04,-107.04 978.18,-105.91 984.3,-104.7"/>
<polygon fill="black" stroke="black" points="984.66,-108.2 993.75,-102.75 983.25,-101.34 984.66,-108.2"/>
</g>
<!-- :core:database -->
<g id="node16" class="node">
<title>:core:database</title>
<ellipse fill="none" stroke="black" cx="391.13" cy="-90" rx="63.63" ry="18"/>
<text text-anchor="middle" x="391.13" y="-84.95" font-family="Times,serif" font-size="14.00">:core:database</text>
</g>
<!-- :core:data&#45;&gt;:core:database -->
<g id="edge22" class="edge">
<title>:core:data&#45;&gt;:core:database</title>
<path fill="none" stroke="red" stroke-width="2" d="M532.8,-148.32C506.06,-137.5 467.25,-121.8 437,-109.56"/>
<polygon fill="red" stroke="red" stroke-width="2" points="440.09,-107.03 429.5,-106.53 437.46,-113.52 440.09,-107.03"/>
</g>
<!-- :core:datastore -->
<g id="node17" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="892.13" cy="-90" rx="65.17" ry="18"/>
<text text-anchor="middle" x="892.13" y="-84.95" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<!-- :core:data&#45;&gt;:core:datastore -->
<g id="edge23" class="edge">
<title>:core:data&#45;&gt;:core:datastore</title>
<path fill="none" stroke="black" d="M600.77,-150.26C608.77,-148.06 617.21,-145.86 625.13,-144 707.95,-124.56 729.76,-124.95 813.13,-108 819.06,-106.79 825.23,-105.51 831.4,-104.21"/>
<polygon fill="black" stroke="black" points="831.9,-107.69 840.95,-102.19 830.45,-100.84 831.9,-107.69"/>
</g>
<!-- :core:network -->
<g id="node18" class="node">
<title>:core:network</title>
<ellipse fill="none" stroke="black" cx="709.13" cy="-90" rx="62.1" ry="18"/>
<text text-anchor="middle" x="709.13" y="-84.95" font-family="Times,serif" font-size="14.00">:core:network</text>
</g>
<!-- :core:data&#45;&gt;:core:network -->
<g id="edge24" class="edge">
<title>:core:data&#45;&gt;:core:network</title>
<path fill="none" stroke="black" d="M592.43,-147.34C613.89,-136.97 643.72,-122.58 667.85,-110.93"/>
<polygon fill="black" stroke="black" points="669.29,-114.12 676.77,-106.62 666.25,-107.82 669.29,-114.12"/>
</g>
<!-- :core:notifications -->
<g id="node19" class="node">
<title>:core:notifications</title>
<ellipse fill="none" stroke="black" cx="551.13" cy="-90" rx="77.97" ry="18"/>
<text text-anchor="middle" x="551.13" y="-84.95" font-family="Times,serif" font-size="14.00">:core:notifications</text>
</g>
<!-- :core:data&#45;&gt;:core:notifications -->
<g id="edge26" class="edge">
<title>:core:data&#45;&gt;:core:notifications</title>
<path fill="none" stroke="black" d="M560.91,-143.7C559.54,-136.32 557.91,-127.52 556.37,-119.25"/>
<polygon fill="black" stroke="black" points="559.85,-118.79 554.58,-109.59 552.96,-120.06 559.85,-118.79"/>
</g>
<!-- :sync:work&#45;&gt;:core:data -->
<g id="edge55" class="edge">
<title>:sync:work&#45;&gt;:core:data</title>
<path fill="none" stroke="black" d="M1003.5,-227.18C929,-218.09 782.21,-199.56 658.13,-180 644.69,-177.88 630.21,-175.38 616.74,-172.95"/>
<polygon fill="black" stroke="black" points="617.66,-169.56 607.19,-171.21 616.4,-176.44 617.66,-169.56"/>
</g>
<!-- :sync:work&#45;&gt;:core:analytics -->
<g id="edge54" class="edge">
<title>:sync:work&#45;&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M1051.02,-215.59C1049.5,-191.61 1046.75,-148.14 1044.93,-119.42"/>
<polygon fill="black" stroke="black" points="1048.43,-119.38 1044.31,-109.62 1041.45,-119.82 1048.43,-119.38"/>
</g>
<!-- :core:domain&#45;&gt;:core:data -->
<g id="edge35" class="edge">
<title>:core:domain&#45;&gt;:core:data</title>
<path fill="none" stroke="red" stroke-width="2" d="M307.48,-222.29C363.12,-209.44 456.09,-187.96 513.55,-174.68"/>
<polygon fill="red" stroke="red" stroke-width="2" points="512.64,-178.49 521.6,-172.83 511.06,-171.67 512.64,-178.49"/>
</g>
<!-- :core:domain&#45;&gt;:core:model -->
<g id="edge36" class="edge">
<title>:core:domain&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M258.85,-215.58C255.67,-183.81 253.43,-115.69 285.13,-72 297.97,-54.29 318.43,-42.15 337.96,-34.01"/>
<polygon fill="black" stroke="black" points="339.18,-37.29 347.25,-30.43 336.66,-30.76 339.18,-37.29"/>
</g>
<!-- :core:database&#45;&gt;:core:model -->
<g id="edge27" class="edge">
<title>:core:database&#45;&gt;:core:model</title>
<path fill="none" stroke="red" stroke-width="2" d="M391.13,-71.7C391.13,-64.41 391.13,-55.73 391.13,-47.54"/>
<polygon fill="red" stroke="red" stroke-width="2" points="394.63,-49.13 391.13,-39.13 387.63,-49.13 394.63,-49.13"/>
</g>
<!-- :core:datastore&#45;&gt;:core:common -->
<g id="edge30" class="edge">
<title>:core:datastore&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M862.83,-73.46C844.12,-63.54 819.63,-50.56 799.26,-39.75"/>
<polygon fill="black" stroke="black" points="801.15,-36.79 790.68,-35.2 797.87,-42.98 801.15,-36.79"/>
</g>
<!-- :core:datastore&#45;&gt;:core:model -->
<g id="edge29" class="edge">
<title>:core:datastore&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M836.32,-80.36C818.39,-77.63 798.43,-74.64 780.13,-72 664.78,-55.39 530,-37.33 453.19,-27.16"/>
<polygon fill="black" stroke="black" points="453.7,-23.7 443.33,-25.86 452.78,-30.64 453.7,-23.7"/>
</g>
<!-- :core:datastore&#45;proto -->
<g id="node20" class="node">
<title>:core:datastore&#45;proto</title>
<ellipse fill="none" stroke="black" cx="931.13" cy="-18" rx="87.69" ry="18"/>
<text text-anchor="middle" x="931.13" y="-12.95" font-family="Times,serif" font-size="14.00">:core:datastore&#45;proto</text>
</g>
<!-- :core:datastore&#45;&gt;:core:datastore&#45;proto -->
<g id="edge28" class="edge">
<title>:core:datastore&#45;&gt;:core:datastore&#45;proto</title>
<path fill="none" stroke="black" d="M901.57,-72.05C905.91,-64.26 911.17,-54.82 916.04,-46.08"/>
<polygon fill="black" stroke="black" points="918.96,-48.04 920.77,-37.6 912.84,-44.63 918.96,-48.04"/>
</g>
<!-- :core:network&#45;&gt;:core:common -->
<g id="edge31" class="edge">
<title>:core:network&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M721.47,-72.05C727.42,-63.89 734.69,-53.91 741.31,-44.82"/>
<polygon fill="black" stroke="black" points="743.94,-47.16 747,-37.01 738.29,-43.03 743.94,-47.16"/>
</g>
<!-- :core:network&#45;&gt;:core:model -->
<g id="edge32" class="edge">
<title>:core:network&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M662.57,-77.69C654.43,-75.76 646.04,-73.8 638.13,-72 573.05,-57.23 498.08,-41.3 447.97,-30.8"/>
<polygon fill="black" stroke="black" points="448.71,-27.38 438.2,-28.76 447.28,-34.23 448.71,-27.38"/>
</g>
<!-- :core:notifications&#45;&gt;:core:common -->
<g id="edge34" class="edge">
<title>:core:notifications&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M594.18,-74.58C627.64,-63.37 674.14,-47.8 709.41,-35.99"/>
<polygon fill="black" stroke="black" points="710.16,-39.43 718.53,-32.93 707.94,-32.79 710.16,-39.43"/>
</g>
<!-- :core:notifications&#45;&gt;:core:model -->
<g id="edge33" class="edge">
<title>:core:notifications&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M516.01,-73.64C491.68,-62.99 459.15,-48.76 433.39,-37.49"/>
<polygon fill="black" stroke="black" points="435.12,-34.43 424.55,-33.62 432.31,-40.84 435.12,-34.43"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="449pt" height="188pt"
viewBox="0.00 0.00 448.51 188.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 184)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-184 444.51,-184 444.51,4 -4,4"/>
<!-- :app&#45;nia&#45;catalog -->
<g id="node1" class="node">
<title>:app&#45;nia&#45;catalog</title>
<ellipse fill="none" stroke="black" cx="164.06" cy="-162" rx="70.8" ry="18"/>
<text text-anchor="middle" x="164.06" y="-156.95" font-family="Times,serif" font-size="14.00">:app&#45;nia&#45;catalog</text>
</g>
<!-- :core:designsystem -->
<g id="node2" class="node">
<title>:core:designsystem</title>
<ellipse fill="none" stroke="black" cx="82.06" cy="-18" rx="82.06" ry="18"/>
<text text-anchor="middle" x="82.06" y="-12.95" font-family="Times,serif" font-size="14.00">:core:designsystem</text>
</g>
<!-- :app&#45;nia&#45;catalog&#45;&gt;:core:designsystem -->
<g id="edge1" class="edge">
<title>:app&#45;nia&#45;catalog&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M154.17,-143.87C140.13,-119.56 114.29,-74.82 97.66,-46.01"/>
<polygon fill="black" stroke="black" points="100.77,-44.41 92.74,-37.5 94.71,-47.91 100.77,-44.41"/>
</g>
<!-- :core:ui -->
<g id="node3" class="node">
<title>:core:ui</title>
<ellipse fill="none" stroke="black" cx="222.06" cy="-90" rx="39.07" ry="18"/>
<text text-anchor="middle" x="222.06" y="-84.95" font-family="Times,serif" font-size="14.00">:core:ui</text>
</g>
<!-- :app&#45;nia&#45;catalog&#45;&gt;:core:ui -->
<g id="edge2" class="edge">
<title>:app&#45;nia&#45;catalog&#45;&gt;:core:ui</title>
<path fill="none" stroke="red" stroke-width="2" d="M178.1,-144.05C185.04,-135.68 193.55,-125.4 201.23,-116.13"/>
<polygon fill="red" stroke="red" stroke-width="2" points="202.93,-119.57 206.62,-109.64 197.54,-115.11 202.93,-119.57"/>
</g>
<!-- :core:ui&#45;&gt;:core:designsystem -->
<g id="edge4" class="edge">
<title>:core:ui&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M196.38,-76.16C176.17,-66.05 147.57,-51.76 124.04,-39.99"/>
<polygon fill="black" stroke="black" points="125.83,-36.97 115.32,-35.63 122.7,-43.23 125.83,-36.97"/>
</g>
<!-- :core:analytics -->
<g id="node4" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="247.06" cy="-18" rx="64.66" ry="18"/>
<text text-anchor="middle" x="247.06" y="-12.95" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
<!-- :core:ui&#45;&gt;:core:analytics -->
<g id="edge3" class="edge">
<title>:core:ui&#45;&gt;:core:analytics</title>
<path fill="none" stroke="red" stroke-width="2" d="M228.11,-72.05C230.8,-64.52 234.05,-55.44 237.08,-46.94"/>
<polygon fill="red" stroke="red" stroke-width="2" points="239.8,-49.72 239.87,-39.13 233.21,-47.37 239.8,-49.72"/>
</g>
<!-- :core:model -->
<g id="node5" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="385.06" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="385.06" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:ui&#45;&gt;:core:model -->
<g id="edge5" class="edge">
<title>:core:ui&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M250.08,-76.97C275.45,-66.07 313.21,-49.86 342.35,-37.34"/>
<polygon fill="black" stroke="black" points="343.45,-40.68 351.26,-33.52 340.69,-34.25 343.45,-40.68"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="137pt" height="44pt"
viewBox="0.00 0.00 137.32 44.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 40)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-40 133.32,-40 133.32,4 -4,4"/>
<!-- :core:analytics -->
<g id="node1" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="64.66" cy="-18" rx="64.66" ry="18"/>
<text text-anchor="middle" x="64.66" y="-12.95" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 863 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="138pt" height="44pt"
viewBox="0.00 0.00 138.34 44.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 40)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-40 134.34,-40 134.34,4 -4,4"/>
<!-- :core:common -->
<g id="node1" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="65.17" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="65.17" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 854 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="763pt" height="188pt"
viewBox="0.00 0.00 762.55 188.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 184)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-184 758.55,-184 758.55,4 -4,4"/>
<!-- :core:data -->
<g id="node1" class="node">
<title>:core:data</title>
<ellipse fill="none" stroke="black" cx="315.89" cy="-162" rx="47.26" ry="18"/>
<text text-anchor="middle" x="315.89" y="-156.95" font-family="Times,serif" font-size="14.00">:core:data</text>
</g>
<!-- :core:common -->
<g id="node2" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="87.89" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="87.89" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<!-- :core:data&#45;&gt;:core:common -->
<g id="edge1" class="edge">
<title>:core:data&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M268.42,-160.03C190.25,-157.31 39.78,-147.01 6.89,-108 -3.42,-95.77 -0.72,-86.07 6.89,-72 14.54,-57.84 27.81,-46.86 41.41,-38.66"/>
<polygon fill="black" stroke="black" points="42.93,-41.82 49.97,-33.91 39.53,-35.7 42.93,-41.82"/>
</g>
<!-- :core:database -->
<g id="node3" class="node">
<title>:core:database</title>
<ellipse fill="none" stroke="black" cx="543.89" cy="-90" rx="63.63" ry="18"/>
<text text-anchor="middle" x="543.89" y="-84.95" font-family="Times,serif" font-size="14.00">:core:database</text>
</g>
<!-- :core:data&#45;&gt;:core:database -->
<g id="edge2" class="edge">
<title>:core:data&#45;&gt;:core:database</title>
<path fill="none" stroke="red" stroke-width="2" d="M352.02,-149.91C389.56,-138.38 448.68,-120.23 491.38,-107.12"/>
<polygon fill="red" stroke="red" stroke-width="2" points="490.71,-110.99 499.24,-104.71 488.65,-104.3 490.71,-110.99"/>
</g>
<!-- :core:datastore -->
<g id="node4" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="396.89" cy="-90" rx="65.17" ry="18"/>
<text text-anchor="middle" x="396.89" y="-84.95" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<!-- :core:data&#45;&gt;:core:datastore -->
<g id="edge3" class="edge">
<title>:core:data&#45;&gt;:core:datastore</title>
<path fill="none" stroke="black" d="M334.27,-145.12C344.61,-136.18 357.73,-124.85 369.28,-114.86"/>
<polygon fill="black" stroke="black" points="371.31,-117.73 376.59,-108.54 366.74,-112.43 371.31,-117.73"/>
</g>
<!-- :core:network -->
<g id="node5" class="node">
<title>:core:network</title>
<ellipse fill="none" stroke="black" cx="77.89" cy="-90" rx="62.1" ry="18"/>
<text text-anchor="middle" x="77.89" y="-84.95" font-family="Times,serif" font-size="14.00">:core:network</text>
</g>
<!-- :core:data&#45;&gt;:core:network -->
<g id="edge4" class="edge">
<title>:core:data&#45;&gt;:core:network</title>
<path fill="none" stroke="black" d="M279.22,-150.22C239.68,-138.58 176.33,-119.95 131.26,-106.7"/>
<polygon fill="black" stroke="black" points="132.26,-103.34 121.68,-103.88 130.28,-110.06 132.26,-103.34"/>
</g>
<!-- :core:analytics -->
<g id="node6" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="689.89" cy="-90" rx="64.66" ry="18"/>
<text text-anchor="middle" x="689.89" y="-84.95" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
<!-- :core:data&#45;&gt;:core:analytics -->
<g id="edge5" class="edge">
<title>:core:data&#45;&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M358.9,-154.18C417.44,-144.73 525.39,-126.69 616.89,-108 621.48,-107.06 626.22,-106.05 630.98,-105.01"/>
<polygon fill="black" stroke="black" points="631.59,-108.46 640.59,-102.86 630.07,-101.63 631.59,-108.46"/>
</g>
<!-- :core:notifications -->
<g id="node7" class="node">
<title>:core:notifications</title>
<ellipse fill="none" stroke="black" cx="235.89" cy="-90" rx="77.97" ry="18"/>
<text text-anchor="middle" x="235.89" y="-84.95" font-family="Times,serif" font-size="14.00">:core:notifications</text>
</g>
<!-- :core:data&#45;&gt;:core:notifications -->
<g id="edge6" class="edge">
<title>:core:data&#45;&gt;:core:notifications</title>
<path fill="none" stroke="black" d="M297.74,-145.12C287.7,-136.33 275.01,-125.23 263.74,-115.37"/>
<polygon fill="black" stroke="black" points="266.06,-112.75 256.23,-108.79 261.45,-118.01 266.06,-112.75"/>
</g>
<!-- :core:model -->
<g id="node8" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="235.89" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="235.89" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:database&#45;&gt;:core:model -->
<g id="edge7" class="edge">
<title>:core:database&#45;&gt;:core:model</title>
<path fill="none" stroke="red" stroke-width="2" d="M496.02,-77.7C487.65,-75.77 479.02,-73.8 470.89,-72 395.06,-55.19 375.45,-53.98 299.89,-36 296.08,-35.09 292.16,-34.13 288.21,-33.14"/>
<polygon fill="red" stroke="red" stroke-width="2" points="290.61,-30.14 280.06,-31.04 288.87,-36.92 290.61,-30.14"/>
</g>
<!-- :core:datastore&#45;&gt;:core:common -->
<g id="edge10" class="edge">
<title>:core:datastore&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M347.94,-77.72C339.58,-75.81 330.99,-73.84 322.89,-72 263.62,-58.51 195.9,-43.25 148.43,-32.58"/>
<polygon fill="black" stroke="black" points="149.42,-29.22 138.89,-30.44 147.88,-36.05 149.42,-29.22"/>
</g>
<!-- :core:datastore&#45;&gt;:core:model -->
<g id="edge9" class="edge">
<title>:core:datastore&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M363.14,-74.33C338.58,-63.65 305.15,-49.11 278.71,-37.62"/>
<polygon fill="black" stroke="black" points="280.18,-34.44 269.61,-33.66 277.39,-40.86 280.18,-34.44"/>
</g>
<!-- :core:datastore&#45;proto -->
<g id="node9" class="node">
<title>:core:datastore&#45;proto</title>
<ellipse fill="none" stroke="black" cx="396.89" cy="-18" rx="87.69" ry="18"/>
<text text-anchor="middle" x="396.89" y="-12.95" font-family="Times,serif" font-size="14.00">:core:datastore&#45;proto</text>
</g>
<!-- :core:datastore&#45;&gt;:core:datastore&#45;proto -->
<g id="edge8" class="edge">
<title>:core:datastore&#45;&gt;:core:datastore&#45;proto</title>
<path fill="none" stroke="black" d="M396.89,-71.7C396.89,-64.41 396.89,-55.73 396.89,-47.54"/>
<polygon fill="black" stroke="black" points="400.39,-47.62 396.89,-37.62 393.39,-47.62 400.39,-47.62"/>
</g>
<!-- :core:network&#45;&gt;:core:common -->
<g id="edge11" class="edge">
<title>:core:network&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M80.36,-71.7C81.42,-64.32 82.67,-55.52 83.85,-47.25"/>
<polygon fill="black" stroke="black" points="87.28,-48 85.23,-37.6 80.35,-47.01 87.28,-48"/>
</g>
<!-- :core:network&#45;&gt;:core:model -->
<g id="edge12" class="edge">
<title>:core:network&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M111.01,-74.33C135.11,-63.65 167.92,-49.11 193.87,-37.62"/>
<polygon fill="black" stroke="black" points="195.05,-40.92 202.78,-33.67 192.21,-34.52 195.05,-40.92"/>
</g>
<!-- :core:notifications&#45;&gt;:core:common -->
<g id="edge14" class="edge">
<title>:core:notifications&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M202.68,-73.29C181.17,-63.12 152.98,-49.79 129.92,-38.88"/>
<polygon fill="black" stroke="black" points="131.63,-35.82 121.09,-34.7 128.64,-42.14 131.63,-35.82"/>
</g>
<!-- :core:notifications&#45;&gt;:core:model -->
<g id="edge13" class="edge">
<title>:core:notifications&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M235.89,-71.7C235.89,-64.41 235.89,-55.73 235.89,-47.54"/>
<polygon fill="black" stroke="black" points="239.39,-47.62 235.89,-37.62 232.39,-47.62 239.39,-47.62"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="763pt" height="260pt"
viewBox="0.00 0.00 762.55 260.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 256)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-256 758.55,-256 758.55,4 -4,4"/>
<!-- :core:data&#45;test -->
<g id="node1" class="node">
<title>:core:data&#45;test</title>
<ellipse fill="none" stroke="black" cx="315.89" cy="-234" rx="63.12" ry="18"/>
<text text-anchor="middle" x="315.89" y="-228.95" font-family="Times,serif" font-size="14.00">:core:data&#45;test</text>
</g>
<!-- :core:data -->
<g id="node2" class="node">
<title>:core:data</title>
<ellipse fill="none" stroke="black" cx="315.89" cy="-162" rx="47.26" ry="18"/>
<text text-anchor="middle" x="315.89" y="-156.95" font-family="Times,serif" font-size="14.00">:core:data</text>
</g>
<!-- :core:data&#45;test&#45;&gt;:core:data -->
<g id="edge1" class="edge">
<title>:core:data&#45;test&#45;&gt;:core:data</title>
<path fill="none" stroke="red" stroke-width="2" d="M315.89,-215.7C315.89,-208.41 315.89,-199.73 315.89,-191.54"/>
<polygon fill="red" stroke="red" stroke-width="2" points="319.39,-193.13 315.89,-183.13 312.39,-193.13 319.39,-193.13"/>
</g>
<!-- :core:common -->
<g id="node3" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="87.89" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="87.89" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<!-- :core:data&#45;&gt;:core:common -->
<g id="edge2" class="edge">
<title>:core:data&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M268.42,-160.03C190.25,-157.31 39.78,-147.01 6.89,-108 -3.42,-95.77 -0.72,-86.07 6.89,-72 14.54,-57.84 27.81,-46.86 41.41,-38.66"/>
<polygon fill="black" stroke="black" points="42.93,-41.82 49.97,-33.91 39.53,-35.7 42.93,-41.82"/>
</g>
<!-- :core:database -->
<g id="node4" class="node">
<title>:core:database</title>
<ellipse fill="none" stroke="black" cx="543.89" cy="-90" rx="63.63" ry="18"/>
<text text-anchor="middle" x="543.89" y="-84.95" font-family="Times,serif" font-size="14.00">:core:database</text>
</g>
<!-- :core:data&#45;&gt;:core:database -->
<g id="edge3" class="edge">
<title>:core:data&#45;&gt;:core:database</title>
<path fill="none" stroke="red" stroke-width="2" d="M352.02,-149.91C389.56,-138.38 448.68,-120.23 491.38,-107.12"/>
<polygon fill="red" stroke="red" stroke-width="2" points="490.71,-110.99 499.24,-104.71 488.65,-104.3 490.71,-110.99"/>
</g>
<!-- :core:datastore -->
<g id="node5" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="396.89" cy="-90" rx="65.17" ry="18"/>
<text text-anchor="middle" x="396.89" y="-84.95" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<!-- :core:data&#45;&gt;:core:datastore -->
<g id="edge4" class="edge">
<title>:core:data&#45;&gt;:core:datastore</title>
<path fill="none" stroke="black" d="M334.27,-145.12C344.61,-136.18 357.73,-124.85 369.28,-114.86"/>
<polygon fill="black" stroke="black" points="371.31,-117.73 376.59,-108.54 366.74,-112.43 371.31,-117.73"/>
</g>
<!-- :core:network -->
<g id="node6" class="node">
<title>:core:network</title>
<ellipse fill="none" stroke="black" cx="77.89" cy="-90" rx="62.1" ry="18"/>
<text text-anchor="middle" x="77.89" y="-84.95" font-family="Times,serif" font-size="14.00">:core:network</text>
</g>
<!-- :core:data&#45;&gt;:core:network -->
<g id="edge5" class="edge">
<title>:core:data&#45;&gt;:core:network</title>
<path fill="none" stroke="black" d="M279.22,-150.22C239.68,-138.58 176.33,-119.95 131.26,-106.7"/>
<polygon fill="black" stroke="black" points="132.26,-103.34 121.68,-103.88 130.28,-110.06 132.26,-103.34"/>
</g>
<!-- :core:analytics -->
<g id="node7" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="689.89" cy="-90" rx="64.66" ry="18"/>
<text text-anchor="middle" x="689.89" y="-84.95" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
<!-- :core:data&#45;&gt;:core:analytics -->
<g id="edge6" class="edge">
<title>:core:data&#45;&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M358.9,-154.18C417.44,-144.73 525.39,-126.69 616.89,-108 621.48,-107.06 626.22,-106.05 630.98,-105.01"/>
<polygon fill="black" stroke="black" points="631.59,-108.46 640.59,-102.86 630.07,-101.63 631.59,-108.46"/>
</g>
<!-- :core:notifications -->
<g id="node8" class="node">
<title>:core:notifications</title>
<ellipse fill="none" stroke="black" cx="235.89" cy="-90" rx="77.97" ry="18"/>
<text text-anchor="middle" x="235.89" y="-84.95" font-family="Times,serif" font-size="14.00">:core:notifications</text>
</g>
<!-- :core:data&#45;&gt;:core:notifications -->
<g id="edge7" class="edge">
<title>:core:data&#45;&gt;:core:notifications</title>
<path fill="none" stroke="black" d="M297.74,-145.12C287.7,-136.33 275.01,-125.23 263.74,-115.37"/>
<polygon fill="black" stroke="black" points="266.06,-112.75 256.23,-108.79 261.45,-118.01 266.06,-112.75"/>
</g>
<!-- :core:model -->
<g id="node9" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="235.89" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="235.89" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:database&#45;&gt;:core:model -->
<g id="edge8" class="edge">
<title>:core:database&#45;&gt;:core:model</title>
<path fill="none" stroke="red" stroke-width="2" d="M496.02,-77.7C487.65,-75.77 479.02,-73.8 470.89,-72 395.06,-55.19 375.45,-53.98 299.89,-36 296.08,-35.09 292.16,-34.13 288.21,-33.14"/>
<polygon fill="red" stroke="red" stroke-width="2" points="290.61,-30.14 280.06,-31.04 288.87,-36.92 290.61,-30.14"/>
</g>
<!-- :core:datastore&#45;&gt;:core:common -->
<g id="edge11" class="edge">
<title>:core:datastore&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M347.94,-77.72C339.58,-75.81 330.99,-73.84 322.89,-72 263.62,-58.51 195.9,-43.25 148.43,-32.58"/>
<polygon fill="black" stroke="black" points="149.42,-29.22 138.89,-30.44 147.88,-36.05 149.42,-29.22"/>
</g>
<!-- :core:datastore&#45;&gt;:core:model -->
<g id="edge10" class="edge">
<title>:core:datastore&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M363.14,-74.33C338.58,-63.65 305.15,-49.11 278.71,-37.62"/>
<polygon fill="black" stroke="black" points="280.18,-34.44 269.61,-33.66 277.39,-40.86 280.18,-34.44"/>
</g>
<!-- :core:datastore&#45;proto -->
<g id="node10" class="node">
<title>:core:datastore&#45;proto</title>
<ellipse fill="none" stroke="black" cx="396.89" cy="-18" rx="87.69" ry="18"/>
<text text-anchor="middle" x="396.89" y="-12.95" font-family="Times,serif" font-size="14.00">:core:datastore&#45;proto</text>
</g>
<!-- :core:datastore&#45;&gt;:core:datastore&#45;proto -->
<g id="edge9" class="edge">
<title>:core:datastore&#45;&gt;:core:datastore&#45;proto</title>
<path fill="none" stroke="black" d="M396.89,-71.7C396.89,-64.41 396.89,-55.73 396.89,-47.54"/>
<polygon fill="black" stroke="black" points="400.39,-47.62 396.89,-37.62 393.39,-47.62 400.39,-47.62"/>
</g>
<!-- :core:network&#45;&gt;:core:common -->
<g id="edge12" class="edge">
<title>:core:network&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M80.36,-71.7C81.42,-64.32 82.67,-55.52 83.85,-47.25"/>
<polygon fill="black" stroke="black" points="87.28,-48 85.23,-37.6 80.35,-47.01 87.28,-48"/>
</g>
<!-- :core:network&#45;&gt;:core:model -->
<g id="edge13" class="edge">
<title>:core:network&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M111.01,-74.33C135.11,-63.65 167.92,-49.11 193.87,-37.62"/>
<polygon fill="black" stroke="black" points="195.05,-40.92 202.78,-33.67 192.21,-34.52 195.05,-40.92"/>
</g>
<!-- :core:notifications&#45;&gt;:core:common -->
<g id="edge15" class="edge">
<title>:core:notifications&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M202.68,-73.29C181.17,-63.12 152.98,-49.79 129.92,-38.88"/>
<polygon fill="black" stroke="black" points="131.63,-35.82 121.09,-34.7 128.64,-42.14 131.63,-35.82"/>
</g>
<!-- :core:notifications&#45;&gt;:core:model -->
<g id="edge14" class="edge">
<title>:core:notifications&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M235.89,-71.7C235.89,-64.41 235.89,-55.73 235.89,-47.54"/>
<polygon fill="black" stroke="black" points="239.39,-47.62 235.89,-37.62 232.39,-47.62 239.39,-47.62"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="135pt" height="116pt"
viewBox="0.00 0.00 135.27 116.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 112)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-112 131.27,-112 131.27,4 -4,4"/>
<!-- :core:database -->
<g id="node1" class="node">
<title>:core:database</title>
<ellipse fill="none" stroke="black" cx="63.63" cy="-90" rx="63.63" ry="18"/>
<text text-anchor="middle" x="63.63" y="-84.95" font-family="Times,serif" font-size="14.00">:core:database</text>
</g>
<!-- :core:model -->
<g id="node2" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="63.63" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="63.63" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:database&#45;&gt;:core:model -->
<g id="edge1" class="edge">
<title>:core:database&#45;&gt;:core:model</title>
<path fill="none" stroke="red" stroke-width="2" d="M63.63,-71.7C63.63,-64.41 63.63,-55.73 63.63,-47.54"/>
<polygon fill="red" stroke="red" stroke-width="2" points="67.13,-49.13 63.63,-39.13 60.13,-49.13 67.13,-49.13"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="461pt" height="116pt"
viewBox="0.00 0.00 460.86 116.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 112)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-112 456.86,-112 456.86,4 -4,4"/>
<!-- :core:datastore -->
<g id="node1" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="248.69" cy="-90" rx="65.17" ry="18"/>
<text text-anchor="middle" x="248.69" y="-84.95" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<!-- :core:datastore&#45;proto -->
<g id="node2" class="node">
<title>:core:datastore&#45;proto</title>
<ellipse fill="none" stroke="black" cx="87.69" cy="-18" rx="87.69" ry="18"/>
<text text-anchor="middle" x="87.69" y="-12.95" font-family="Times,serif" font-size="14.00">:core:datastore&#45;proto</text>
</g>
<!-- :core:datastore&#45;&gt;:core:datastore&#45;proto -->
<g id="edge1" class="edge">
<title>:core:datastore&#45;&gt;:core:datastore&#45;proto</title>
<path fill="none" stroke="red" stroke-width="2" d="M214.94,-74.33C191.52,-64.14 160.02,-50.45 134.22,-39.23"/>
<polygon fill="red" stroke="red" stroke-width="2" points="137.26,-36.73 126.69,-35.96 134.47,-43.15 137.26,-36.73"/>
</g>
<!-- :core:model -->
<g id="node3" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="248.69" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="248.69" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:datastore&#45;&gt;:core:model -->
<g id="edge2" class="edge">
<title>:core:datastore&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M248.69,-71.7C248.69,-64.41 248.69,-55.73 248.69,-47.54"/>
<polygon fill="black" stroke="black" points="252.19,-47.62 248.69,-37.62 245.19,-47.62 252.19,-47.62"/>
</g>
<!-- :core:common -->
<g id="node4" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="387.69" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="387.69" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<!-- :core:datastore&#45;&gt;:core:common -->
<g id="edge3" class="edge">
<title>:core:datastore&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M279.19,-73.64C299.11,-63.61 325.34,-50.4 347.03,-39.48"/>
<polygon fill="black" stroke="black" points="348.55,-42.63 355.91,-35.01 345.4,-36.38 348.55,-42.63"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="183pt" height="44pt"
viewBox="0.00 0.00 183.38 44.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 40)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-40 179.38,-40 179.38,4 -4,4"/>
<!-- :core:datastore&#45;proto -->
<g id="node1" class="node">
<title>:core:datastore&#45;proto</title>
<ellipse fill="none" stroke="black" cx="87.69" cy="-18" rx="87.69" ry="18"/>
<text text-anchor="middle" x="87.69" y="-12.95" font-family="Times,serif" font-size="14.00">:core:datastore&#45;proto</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 893 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="461pt" height="188pt"
viewBox="0.00 0.00 460.62 188.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 184)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-184 456.62,-184 456.62,4 -4,4"/>
<!-- :core:datastore&#45;test -->
<g id="node1" class="node">
<title>:core:datastore&#45;test</title>
<ellipse fill="none" stroke="black" cx="150.17" cy="-162" rx="81.04" ry="18"/>
<text text-anchor="middle" x="150.17" y="-156.95" font-family="Times,serif" font-size="14.00">:core:datastore&#45;test</text>
</g>
<!-- :core:common -->
<g id="node2" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="65.17" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="65.17" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<!-- :core:datastore&#45;test&#45;&gt;:core:common -->
<g id="edge1" class="edge">
<title>:core:datastore&#45;test&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M139.92,-143.87C125.37,-119.56 98.58,-74.82 81.34,-46.01"/>
<polygon fill="black" stroke="black" points="84.38,-44.27 76.24,-37.49 78.37,-47.87 84.38,-44.27"/>
</g>
<!-- :core:datastore -->
<g id="node3" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="216.17" cy="-90" rx="65.17" ry="18"/>
<text text-anchor="middle" x="216.17" y="-84.95" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<!-- :core:datastore&#45;test&#45;&gt;:core:datastore -->
<g id="edge2" class="edge">
<title>:core:datastore&#45;test&#45;&gt;:core:datastore</title>
<path fill="none" stroke="red" stroke-width="2" d="M166.15,-144.05C174.09,-135.63 183.85,-125.28 192.63,-115.97"/>
<polygon fill="red" stroke="red" stroke-width="2" points="193.95,-119.67 198.26,-109.99 188.86,-114.87 193.95,-119.67"/>
</g>
<!-- :core:datastore&#45;&gt;:core:common -->
<g id="edge5" class="edge">
<title>:core:datastore&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M183.78,-73.98C161.58,-63.7 131.92,-49.94 107.81,-38.77"/>
<polygon fill="black" stroke="black" points="109.43,-35.66 98.88,-34.63 106.48,-42.01 109.43,-35.66"/>
</g>
<!-- :core:datastore&#45;proto -->
<g id="node4" class="node">
<title>:core:datastore&#45;proto</title>
<ellipse fill="none" stroke="black" cx="236.17" cy="-18" rx="87.69" ry="18"/>
<text text-anchor="middle" x="236.17" y="-12.95" font-family="Times,serif" font-size="14.00">:core:datastore&#45;proto</text>
</g>
<!-- :core:datastore&#45;&gt;:core:datastore&#45;proto -->
<g id="edge3" class="edge">
<title>:core:datastore&#45;&gt;:core:datastore&#45;proto</title>
<path fill="none" stroke="red" stroke-width="2" d="M221.11,-71.7C223.25,-64.24 225.79,-55.32 228.18,-46.97"/>
<polygon fill="red" stroke="red" stroke-width="2" points="231.07,-49.59 230.45,-39.01 224.34,-47.67 231.07,-49.59"/>
</g>
<!-- :core:model -->
<g id="node5" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="397.17" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="397.17" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:datastore&#45;&gt;:core:model -->
<g id="edge4" class="edge">
<title>:core:datastore&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M252.8,-74.83C281.44,-63.76 321.41,-48.3 352.03,-36.46"/>
<polygon fill="black" stroke="black" points="352.96,-39.85 361.03,-32.98 350.44,-33.32 352.96,-39.85"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="172pt" height="44pt"
viewBox="0.00 0.00 172.12 44.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 40)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-40 168.12,-40 168.12,4 -4,4"/>
<!-- :core:designsystem -->
<g id="node1" class="node">
<title>:core:designsystem</title>
<ellipse fill="none" stroke="black" cx="82.06" cy="-18" rx="82.06" ry="18"/>
<text text-anchor="middle" x="82.06" y="-12.95" font-family="Times,serif" font-size="14.00">:core:designsystem</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 872 B

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="829pt" height="260pt"
viewBox="0.00 0.00 829.46 260.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 256)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-256 825.46,-256 825.46,4 -4,4"/>
<!-- :core:domain -->
<g id="node1" class="node">
<title>:core:domain</title>
<ellipse fill="none" stroke="black" cx="623.69" cy="-234" rx="60.05" ry="18"/>
<text text-anchor="middle" x="623.69" y="-228.95" font-family="Times,serif" font-size="14.00">:core:domain</text>
</g>
<!-- :core:data -->
<g id="node2" class="node">
<title>:core:data</title>
<ellipse fill="none" stroke="black" cx="418.69" cy="-162" rx="47.26" ry="18"/>
<text text-anchor="middle" x="418.69" y="-156.95" font-family="Times,serif" font-size="14.00">:core:data</text>
</g>
<!-- :core:domain&#45;&gt;:core:data -->
<g id="edge1" class="edge">
<title>:core:domain&#45;&gt;:core:data</title>
<path fill="none" stroke="red" stroke-width="2" d="M585.13,-219.83C550.65,-208.06 500.11,-190.8 463.77,-178.39"/>
<polygon fill="red" stroke="red" stroke-width="2" points="466.45,-175.61 455.85,-175.69 464.18,-182.23 466.45,-175.61"/>
</g>
<!-- :core:model -->
<g id="node3" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="576.69" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="576.69" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:domain&#45;&gt;:core:model -->
<g id="edge2" class="edge">
<title>:core:domain&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M662.34,-219.86C730.64,-194.61 860.84,-136.09 809.69,-72 788.92,-45.98 702.6,-31.75 641.09,-24.76"/>
<polygon fill="black" stroke="black" points="641.74,-21.31 631.42,-23.7 640.98,-28.27 641.74,-21.31"/>
</g>
<!-- :core:common -->
<g id="node4" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="338.69" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="338.69" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<!-- :core:data&#45;&gt;:core:common -->
<g id="edge3" class="edge">
<title>:core:data&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M373.15,-156.52C315.42,-149.79 220.86,-134.9 199.69,-108 189.8,-95.43 190.61,-85.18 199.69,-72 215.46,-49.11 242.54,-36.1 268.63,-28.71"/>
<polygon fill="black" stroke="black" points="269.43,-32.11 278.25,-26.24 267.69,-25.33 269.43,-32.11"/>
</g>
<!-- :core:database -->
<g id="node5" class="node">
<title>:core:database</title>
<ellipse fill="none" stroke="black" cx="736.69" cy="-90" rx="63.63" ry="18"/>
<text text-anchor="middle" x="736.69" y="-84.95" font-family="Times,serif" font-size="14.00">:core:database</text>
</g>
<!-- :core:data&#45;&gt;:core:database -->
<g id="edge4" class="edge">
<title>:core:data&#45;&gt;:core:database</title>
<path fill="none" stroke="red" stroke-width="2" d="M459.48,-152.38C508.14,-142.05 591.93,-124.12 663.69,-108 668.26,-106.97 672.99,-105.9 677.74,-104.81"/>
<polygon fill="red" stroke="red" stroke-width="2" points="676.9,-108.6 685.86,-102.94 675.33,-101.78 676.9,-108.6"/>
</g>
<!-- :core:datastore -->
<g id="node6" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="87.69" cy="-90" rx="65.17" ry="18"/>
<text text-anchor="middle" x="87.69" y="-84.95" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<!-- :core:data&#45;&gt;:core:datastore -->
<g id="edge5" class="edge">
<title>:core:data&#45;&gt;:core:datastore</title>
<path fill="none" stroke="black" d="M378.09,-152.52C328.27,-142.11 241.25,-123.86 166.69,-108 160.86,-106.76 154.79,-105.46 148.71,-104.16"/>
<polygon fill="black" stroke="black" points="149.82,-100.82 139.31,-102.14 148.36,-107.66 149.82,-100.82"/>
</g>
<!-- :core:network -->
<g id="node7" class="node">
<title>:core:network</title>
<ellipse fill="none" stroke="black" cx="418.69" cy="-90" rx="62.1" ry="18"/>
<text text-anchor="middle" x="418.69" y="-84.95" font-family="Times,serif" font-size="14.00">:core:network</text>
</g>
<!-- :core:data&#45;&gt;:core:network -->
<g id="edge6" class="edge">
<title>:core:data&#45;&gt;:core:network</title>
<path fill="none" stroke="black" d="M418.69,-143.7C418.69,-136.41 418.69,-127.73 418.69,-119.54"/>
<polygon fill="black" stroke="black" points="422.19,-119.62 418.69,-109.62 415.19,-119.62 422.19,-119.62"/>
</g>
<!-- :core:analytics -->
<g id="node8" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="273.69" cy="-90" rx="64.66" ry="18"/>
<text text-anchor="middle" x="273.69" y="-84.95" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
<!-- :core:data&#45;&gt;:core:analytics -->
<g id="edge7" class="edge">
<title>:core:data&#45;&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M390.39,-147.34C368.99,-137.01 339.3,-122.67 315.21,-111.05"/>
<polygon fill="black" stroke="black" points="316.83,-107.94 306.3,-106.74 313.78,-114.24 316.83,-107.94"/>
</g>
<!-- :core:notifications -->
<g id="node9" class="node">
<title>:core:notifications</title>
<ellipse fill="none" stroke="black" cx="576.69" cy="-90" rx="77.97" ry="18"/>
<text text-anchor="middle" x="576.69" y="-84.95" font-family="Times,serif" font-size="14.00">:core:notifications</text>
</g>
<!-- :core:data&#45;&gt;:core:notifications -->
<g id="edge8" class="edge">
<title>:core:data&#45;&gt;:core:notifications</title>
<path fill="none" stroke="black" d="M448.78,-147.67C472.18,-137.3 505.01,-122.76 531.55,-111"/>
<polygon fill="black" stroke="black" points="532.64,-114.34 540.37,-107.09 529.81,-107.94 532.64,-114.34"/>
</g>
<!-- :core:database&#45;&gt;:core:model -->
<g id="edge9" class="edge">
<title>:core:database&#45;&gt;:core:model</title>
<path fill="none" stroke="red" stroke-width="2" d="M703.15,-74.33C678.75,-63.65 645.52,-49.11 619.24,-37.62"/>
<polygon fill="red" stroke="red" stroke-width="2" points="622.16,-35.07 611.6,-34.27 619.35,-41.49 622.16,-35.07"/>
</g>
<!-- :core:datastore&#45;&gt;:core:model -->
<g id="edge11" class="edge">
<title>:core:datastore&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M143.5,-80.41C161.44,-77.68 181.4,-74.67 199.69,-72 310.88,-55.75 440.67,-37.7 515.31,-27.42"/>
<polygon fill="black" stroke="black" points="515.43,-30.94 524.86,-26.1 514.47,-24 515.43,-30.94"/>
</g>
<!-- :core:datastore&#45;&gt;:core:common -->
<g id="edge12" class="edge">
<title>:core:datastore&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M132.27,-76.57C174.44,-64.81 237.85,-47.12 283.26,-34.46"/>
<polygon fill="black" stroke="black" points="283.95,-37.9 292.64,-31.84 282.07,-31.16 283.95,-37.9"/>
</g>
<!-- :core:datastore&#45;proto -->
<g id="node10" class="node">
<title>:core:datastore&#45;proto</title>
<ellipse fill="none" stroke="black" cx="87.69" cy="-18" rx="87.69" ry="18"/>
<text text-anchor="middle" x="87.69" y="-12.95" font-family="Times,serif" font-size="14.00">:core:datastore&#45;proto</text>
</g>
<!-- :core:datastore&#45;&gt;:core:datastore&#45;proto -->
<g id="edge10" class="edge">
<title>:core:datastore&#45;&gt;:core:datastore&#45;proto</title>
<path fill="none" stroke="black" d="M87.69,-71.7C87.69,-64.41 87.69,-55.73 87.69,-47.54"/>
<polygon fill="black" stroke="black" points="91.19,-47.62 87.69,-37.62 84.19,-47.62 91.19,-47.62"/>
</g>
<!-- :core:network&#45;&gt;:core:model -->
<g id="edge14" class="edge">
<title>:core:network&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M451.81,-74.33C475.91,-63.65 508.72,-49.11 534.67,-37.62"/>
<polygon fill="black" stroke="black" points="535.85,-40.92 543.58,-33.67 533.01,-34.52 535.85,-40.92"/>
</g>
<!-- :core:network&#45;&gt;:core:common -->
<g id="edge13" class="edge">
<title>:core:network&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M399.73,-72.41C389.67,-63.61 377.12,-52.63 366.03,-42.92"/>
<polygon fill="black" stroke="black" points="368.48,-40.42 358.65,-36.47 363.87,-45.69 368.48,-40.42"/>
</g>
<!-- :core:notifications&#45;&gt;:core:model -->
<g id="edge15" class="edge">
<title>:core:notifications&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M576.69,-71.7C576.69,-64.41 576.69,-55.73 576.69,-47.54"/>
<polygon fill="black" stroke="black" points="580.19,-47.62 576.69,-37.62 573.19,-47.62 580.19,-47.62"/>
</g>
<!-- :core:notifications&#45;&gt;:core:common -->
<g id="edge16" class="edge">
<title>:core:notifications&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M529.95,-75.25C490.64,-63.69 434.4,-47.15 393.08,-35"/>
<polygon fill="black" stroke="black" points="394.15,-31.66 383.56,-32.2 392.17,-38.38 394.15,-31.66"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="119pt" height="44pt"
viewBox="0.00 0.00 118.89 44.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 40)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-40 114.89,-40 114.89,4 -4,4"/>
<!-- :core:model -->
<g id="node1" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="55.45" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="55.45" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 851 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="268pt" height="116pt"
viewBox="0.00 0.00 267.62 116.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 112)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-112 263.62,-112 263.62,4 -4,4"/>
<!-- :core:network -->
<g id="node1" class="node">
<title>:core:network</title>
<ellipse fill="none" stroke="black" cx="134.17" cy="-90" rx="62.1" ry="18"/>
<text text-anchor="middle" x="134.17" y="-84.95" font-family="Times,serif" font-size="14.00">:core:network</text>
</g>
<!-- :core:common -->
<g id="node2" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="65.17" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="65.17" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<!-- :core:network&#45;&gt;:core:common -->
<g id="edge1" class="edge">
<title>:core:network&#45;&gt;:core:common</title>
<path fill="none" stroke="red" stroke-width="2" d="M117.82,-72.41C109.31,-63.78 98.75,-53.06 89.32,-43.5"/>
<polygon fill="red" stroke="red" stroke-width="2" points="93.04,-42.29 83.53,-37.63 88.06,-47.2 93.04,-42.29"/>
</g>
<!-- :core:model -->
<g id="node3" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="204.17" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="204.17" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:network&#45;&gt;:core:model -->
<g id="edge2" class="edge">
<title>:core:network&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M150.76,-72.41C159.39,-63.78 170.11,-53.06 179.67,-43.5"/>
<polygon fill="black" stroke="black" points="182.03,-46.09 186.63,-36.54 177.08,-41.14 182.03,-46.09"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="268pt" height="116pt"
viewBox="0.00 0.00 267.62 116.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 112)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-112 263.62,-112 263.62,4 -4,4"/>
<!-- :core:notifications -->
<g id="node1" class="node">
<title>:core:notifications</title>
<ellipse fill="none" stroke="black" cx="124.45" cy="-90" rx="77.97" ry="18"/>
<text text-anchor="middle" x="124.45" y="-84.95" font-family="Times,serif" font-size="14.00">:core:notifications</text>
</g>
<!-- :core:model -->
<g id="node2" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="55.45" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="55.45" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:notifications&#45;&gt;:core:model -->
<g id="edge1" class="edge">
<title>:core:notifications&#45;&gt;:core:model</title>
<path fill="none" stroke="red" stroke-width="2" d="M107.74,-72.05C99.23,-63.42 88.72,-52.76 79.37,-43.27"/>
<polygon fill="red" stroke="red" stroke-width="2" points="83.16,-42.13 73.64,-37.46 78.17,-47.04 83.16,-42.13"/>
</g>
<!-- :core:common -->
<g id="node3" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="194.45" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="194.45" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<!-- :core:notifications&#45;&gt;:core:common -->
<g id="edge2" class="edge">
<title>:core:notifications&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M141.39,-72.05C149.9,-63.54 160.38,-53.07 169.76,-43.68"/>
<polygon fill="black" stroke="black" points="171.99,-46.41 176.59,-36.86 167.04,-41.46 171.99,-46.41"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="320pt" height="116pt"
viewBox="0.00 0.00 320.23 116.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 112)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-112 316.23,-112 316.23,4 -4,4"/>
<!-- :core:screenshot&#45;testing -->
<g id="node1" class="node">
<title>:core:screenshot&#45;testing</title>
<ellipse fill="none" stroke="black" cx="147.17" cy="-90" rx="98.44" ry="18"/>
<text text-anchor="middle" x="147.17" y="-84.95" font-family="Times,serif" font-size="14.00">:core:screenshot&#45;testing</text>
</g>
<!-- :core:common -->
<g id="node2" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="65.17" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="65.17" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<!-- :core:screenshot&#45;testing&#45;&gt;:core:common -->
<g id="edge1" class="edge">
<title>:core:screenshot&#45;testing&#45;&gt;:core:common</title>
<path fill="none" stroke="red" stroke-width="2" d="M127.32,-72.05C117.07,-63.31 104.4,-52.49 93.17,-42.9"/>
<polygon fill="red" stroke="red" stroke-width="2" points="96.73,-41.34 86.86,-37.51 92.19,-46.67 96.73,-41.34"/>
</g>
<!-- :core:designsystem -->
<g id="node3" class="node">
<title>:core:designsystem</title>
<ellipse fill="none" stroke="black" cx="230.17" cy="-18" rx="82.06" ry="18"/>
<text text-anchor="middle" x="230.17" y="-12.95" font-family="Times,serif" font-size="14.00">:core:designsystem</text>
</g>
<!-- :core:screenshot&#45;testing&#45;&gt;:core:designsystem -->
<g id="edge2" class="edge">
<title>:core:screenshot&#45;testing&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M167.26,-72.05C177.56,-63.37 190.28,-52.64 201.58,-43.11"/>
<polygon fill="black" stroke="black" points="203.72,-45.89 209.11,-36.76 199.21,-40.54 203.72,-45.89"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 37 KiB

@ -0,0 +1,199 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="1055pt" height="260pt"
viewBox="0.00 0.00 1054.70 260.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 256)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-256 1050.7,-256 1050.7,4 -4,4"/>
<!-- :core:testing -->
<g id="node1" class="node">
<title>:core:testing</title>
<ellipse fill="none" stroke="black" cx="650.64" cy="-234" rx="56.47" ry="18"/>
<text text-anchor="middle" x="650.64" y="-228.95" font-family="Times,serif" font-size="14.00">:core:testing</text>
</g>
<!-- :core:analytics -->
<g id="node2" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="114.64" cy="-90" rx="64.66" ry="18"/>
<text text-anchor="middle" x="114.64" y="-84.95" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
<!-- :core:testing&#45;&gt;:core:analytics -->
<g id="edge1" class="edge">
<title>:core:testing&#45;&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M608.13,-221.74C512.24,-196.33 279.05,-134.56 171.14,-105.97"/>
<polygon fill="black" stroke="black" points="172.19,-102.63 161.63,-103.45 170.4,-109.39 172.19,-102.63"/>
</g>
<!-- :core:data -->
<g id="node3" class="node">
<title>:core:data</title>
<ellipse fill="none" stroke="black" cx="552.64" cy="-162" rx="47.26" ry="18"/>
<text text-anchor="middle" x="552.64" y="-156.95" font-family="Times,serif" font-size="14.00">:core:data</text>
</g>
<!-- :core:testing&#45;&gt;:core:data -->
<g id="edge2" class="edge">
<title>:core:testing&#45;&gt;:core:data</title>
<path fill="none" stroke="red" stroke-width="2" d="M628.4,-217.12C615.06,-207.59 597.9,-195.33 583.29,-184.89"/>
<polygon fill="red" stroke="red" stroke-width="2" points="586.75,-183.06 576.58,-180.1 582.68,-188.76 586.75,-183.06"/>
</g>
<!-- :core:model -->
<g id="node4" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="455.64" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="455.64" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:testing&#45;&gt;:core:model -->
<g id="edge3" class="edge">
<title>:core:testing&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M594.71,-230.48C457.07,-222.97 108.18,-195.5 40.64,-108 -29.97,-16.52 -58.46,-118.33 391.64,-36 395.73,-35.25 399.93,-34.38 404.15,-33.43"/>
<polygon fill="black" stroke="black" points="404.85,-36.86 413.76,-31.12 403.22,-30.05 404.85,-36.86"/>
</g>
<!-- :core:notifications -->
<g id="node5" class="node">
<title>:core:notifications</title>
<ellipse fill="none" stroke="black" cx="748.64" cy="-90" rx="77.97" ry="18"/>
<text text-anchor="middle" x="748.64" y="-84.95" font-family="Times,serif" font-size="14.00">:core:notifications</text>
</g>
<!-- :core:testing&#45;&gt;:core:notifications -->
<g id="edge4" class="edge">
<title>:core:testing&#45;&gt;:core:notifications</title>
<path fill="none" stroke="black" d="M662.27,-216.15C679.12,-191.73 710.5,-146.26 730.44,-117.38"/>
<polygon fill="black" stroke="black" points="733.06,-119.73 735.86,-109.51 727.3,-115.76 733.06,-119.73"/>
</g>
<!-- :core:common -->
<g id="node6" class="node">
<title>:core:common</title>
<ellipse fill="none" stroke="black" cx="642.64" cy="-18" rx="65.17" ry="18"/>
<text text-anchor="middle" x="642.64" y="-12.95" font-family="Times,serif" font-size="14.00">:core:common</text>
</g>
<!-- :core:testing&#45;&gt;:core:common -->
<g id="edge5" class="edge">
<title>:core:testing&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M688.31,-220.23C755.69,-195.37 885.33,-137.18 835.64,-72 820.35,-51.94 760.32,-37.59 711.22,-28.92"/>
<polygon fill="black" stroke="black" points="711.91,-25.49 701.47,-27.25 710.73,-32.39 711.91,-25.49"/>
</g>
<!-- :core:designsystem -->
<g id="node7" class="node">
<title>:core:designsystem</title>
<ellipse fill="none" stroke="black" cx="964.64" cy="-162" rx="82.06" ry="18"/>
<text text-anchor="middle" x="964.64" y="-156.95" font-family="Times,serif" font-size="14.00">:core:designsystem</text>
</g>
<!-- :core:testing&#45;&gt;:core:designsystem -->
<g id="edge6" class="edge">
<title>:core:testing&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M695.96,-222.9C748.81,-211.12 836.64,-191.53 897.43,-177.98"/>
<polygon fill="black" stroke="black" points="897.97,-181.45 906.97,-175.86 896.45,-174.62 897.97,-181.45"/>
</g>
<!-- :core:data&#45;&gt;:core:analytics -->
<g id="edge11" class="edge">
<title>:core:data&#45;&gt;:core:analytics</title>
<path fill="none" stroke="black" d="M507.77,-155.73C438.7,-147.33 302.32,-129.64 187.64,-108 182.84,-107.09 177.87,-106.09 172.89,-105.03"/>
<polygon fill="black" stroke="black" points="174.03,-101.69 163.51,-102.97 172.53,-108.53 174.03,-101.69"/>
</g>
<!-- :core:data&#45;&gt;:core:notifications -->
<g id="edge12" class="edge">
<title>:core:data&#45;&gt;:core:notifications</title>
<path fill="none" stroke="black" d="M586.33,-148.97C616.79,-138.09 662.07,-121.92 697.07,-109.42"/>
<polygon fill="black" stroke="black" points="697.91,-112.83 706.15,-106.17 695.56,-106.24 697.91,-112.83"/>
</g>
<!-- :core:data&#45;&gt;:core:common -->
<g id="edge7" class="edge">
<title>:core:data&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M580.17,-146.93C595.27,-137.83 613.02,-124.58 623.64,-108 635.18,-90 639.83,-66.15 641.65,-47.67"/>
<polygon fill="black" stroke="black" points="645.12,-48.16 642.37,-37.93 638.14,-47.65 645.12,-48.16"/>
</g>
<!-- :core:database -->
<g id="node8" class="node">
<title>:core:database</title>
<ellipse fill="none" stroke="black" cx="260.64" cy="-90" rx="63.63" ry="18"/>
<text text-anchor="middle" x="260.64" y="-84.95" font-family="Times,serif" font-size="14.00">:core:database</text>
</g>
<!-- :core:data&#45;&gt;:core:database -->
<g id="edge8" class="edge">
<title>:core:data&#45;&gt;:core:database</title>
<path fill="none" stroke="red" stroke-width="2" d="M513.26,-151.56C463.35,-139.59 376.88,-118.87 319.22,-105.04"/>
<polygon fill="red" stroke="red" stroke-width="2" points="321.57,-102.01 311.03,-103.08 319.94,-108.81 321.57,-102.01"/>
</g>
<!-- :core:datastore -->
<g id="node9" class="node">
<title>:core:datastore</title>
<ellipse fill="none" stroke="black" cx="407.64" cy="-90" rx="65.17" ry="18"/>
<text text-anchor="middle" x="407.64" y="-84.95" font-family="Times,serif" font-size="14.00">:core:datastore</text>
</g>
<!-- :core:data&#45;&gt;:core:datastore -->
<g id="edge9" class="edge">
<title>:core:data&#45;&gt;:core:datastore</title>
<path fill="none" stroke="black" d="M524.34,-147.34C503.02,-137.04 473.45,-122.77 449.41,-111.16"/>
<polygon fill="black" stroke="black" points="451.03,-108.06 440.51,-106.87 447.99,-114.37 451.03,-108.06"/>
</g>
<!-- :core:network -->
<g id="node10" class="node">
<title>:core:network</title>
<ellipse fill="none" stroke="black" cx="552.64" cy="-90" rx="62.1" ry="18"/>
<text text-anchor="middle" x="552.64" y="-84.95" font-family="Times,serif" font-size="14.00">:core:network</text>
</g>
<!-- :core:data&#45;&gt;:core:network -->
<g id="edge10" class="edge">
<title>:core:data&#45;&gt;:core:network</title>
<path fill="none" stroke="black" d="M552.64,-143.7C552.64,-136.41 552.64,-127.73 552.64,-119.54"/>
<polygon fill="black" stroke="black" points="556.14,-119.62 552.64,-109.62 549.14,-119.62 556.14,-119.62"/>
</g>
<!-- :core:notifications&#45;&gt;:core:model -->
<g id="edge19" class="edge">
<title>:core:notifications&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M695.92,-76.4C643.47,-63.87 563.35,-44.73 510.25,-32.05"/>
<polygon fill="black" stroke="black" points="511.36,-28.71 500.82,-29.79 509.73,-35.52 511.36,-28.71"/>
</g>
<!-- :core:notifications&#45;&gt;:core:common -->
<g id="edge20" class="edge">
<title>:core:notifications&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M723.79,-72.59C709.6,-63.22 691.62,-51.34 676.19,-41.16"/>
<polygon fill="black" stroke="black" points="678.51,-38.49 668.24,-35.9 674.65,-44.33 678.51,-38.49"/>
</g>
<!-- :core:database&#45;&gt;:core:model -->
<g id="edge13" class="edge">
<title>:core:database&#45;&gt;:core:model</title>
<path fill="none" stroke="red" stroke-width="2" d="M298.7,-75.34C330.21,-64.03 375.1,-47.91 408.81,-35.81"/>
<polygon fill="red" stroke="red" stroke-width="2" points="408.25,-39.73 416.48,-33.06 405.88,-33.14 408.25,-39.73"/>
</g>
<!-- :core:datastore&#45;&gt;:core:model -->
<g id="edge15" class="edge">
<title>:core:datastore&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M419.26,-72.05C424.8,-63.97 431.56,-54.12 437.74,-45.11"/>
<polygon fill="black" stroke="black" points="440.5,-47.26 443.27,-37.04 434.73,-43.31 440.5,-47.26"/>
</g>
<!-- :core:datastore&#45;&gt;:core:common -->
<g id="edge16" class="edge">
<title>:core:datastore&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M450.74,-76.16C489.55,-64.6 546.73,-47.57 588.59,-35.1"/>
<polygon fill="black" stroke="black" points="589.38,-38.52 597.96,-32.31 587.38,-31.81 589.38,-38.52"/>
</g>
<!-- :core:datastore&#45;proto -->
<g id="node11" class="node">
<title>:core:datastore&#45;proto</title>
<ellipse fill="none" stroke="black" cx="294.64" cy="-18" rx="87.69" ry="18"/>
<text text-anchor="middle" x="294.64" y="-12.95" font-family="Times,serif" font-size="14.00">:core:datastore&#45;proto</text>
</g>
<!-- :core:datastore&#45;&gt;:core:datastore&#45;proto -->
<g id="edge14" class="edge">
<title>:core:datastore&#45;&gt;:core:datastore&#45;proto</title>
<path fill="none" stroke="black" d="M382,-73.12C366.87,-63.74 347.47,-51.73 330.81,-41.4"/>
<polygon fill="black" stroke="black" points="332.99,-38.64 322.65,-36.35 329.3,-44.59 332.99,-38.64"/>
</g>
<!-- :core:network&#45;&gt;:core:model -->
<g id="edge18" class="edge">
<title>:core:network&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M530.14,-72.76C517.32,-63.51 501.05,-51.77 486.99,-41.63"/>
<polygon fill="black" stroke="black" points="489.07,-38.81 478.91,-35.79 484.97,-44.48 489.07,-38.81"/>
</g>
<!-- :core:network&#45;&gt;:core:common -->
<g id="edge17" class="edge">
<title>:core:network&#45;&gt;:core:common</title>
<path fill="none" stroke="black" d="M573.52,-72.76C585.13,-63.73 599.79,-52.33 612.62,-42.35"/>
<polygon fill="black" stroke="black" points="614.63,-45.22 620.37,-36.32 610.33,-39.7 614.63,-45.22"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

@ -0,0 +1,55 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Generated by graphviz version 9.0.0 (20230911.1827)
-->
<!-- Title: G Pages: 1 -->
<svg width="449pt" height="116pt"
viewBox="0.00 0.00 449.10 116.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 112)">
<title>G</title>
<polygon fill="white" stroke="none" points="-4,4 -4,-112 445.1,-112 445.1,4 -4,4"/>
<!-- :core:ui -->
<g id="node1" class="node">
<title>:core:ui</title>
<ellipse fill="none" stroke="black" cx="229.66" cy="-90" rx="39.07" ry="18"/>
<text text-anchor="middle" x="229.66" y="-84.95" font-family="Times,serif" font-size="14.00">:core:ui</text>
</g>
<!-- :core:analytics -->
<g id="node2" class="node">
<title>:core:analytics</title>
<ellipse fill="none" stroke="black" cx="64.66" cy="-18" rx="64.66" ry="18"/>
<text text-anchor="middle" x="64.66" y="-12.95" font-family="Times,serif" font-size="14.00">:core:analytics</text>
</g>
<!-- :core:ui&#45;&gt;:core:analytics -->
<g id="edge1" class="edge">
<title>:core:ui&#45;&gt;:core:analytics</title>
<path fill="none" stroke="red" stroke-width="2" d="M201.67,-77.13C176.45,-66.43 138.92,-50.51 109.52,-38.03"/>
<polygon fill="red" stroke="red" stroke-width="2" points="112.45,-35.47 101.88,-34.79 109.72,-41.92 112.45,-35.47"/>
</g>
<!-- :core:designsystem -->
<g id="node3" class="node">
<title>:core:designsystem</title>
<ellipse fill="none" stroke="black" cx="229.66" cy="-18" rx="82.06" ry="18"/>
<text text-anchor="middle" x="229.66" y="-12.95" font-family="Times,serif" font-size="14.00">:core:designsystem</text>
</g>
<!-- :core:ui&#45;&gt;:core:designsystem -->
<g id="edge2" class="edge">
<title>:core:ui&#45;&gt;:core:designsystem</title>
<path fill="none" stroke="black" d="M229.66,-71.7C229.66,-64.41 229.66,-55.73 229.66,-47.54"/>
<polygon fill="black" stroke="black" points="233.16,-47.62 229.66,-37.62 226.16,-47.62 233.16,-47.62"/>
</g>
<!-- :core:model -->
<g id="node4" class="node">
<title>:core:model</title>
<ellipse fill="none" stroke="black" cx="385.66" cy="-18" rx="55.45" ry="18"/>
<text text-anchor="middle" x="385.66" y="-12.95" font-family="Times,serif" font-size="14.00">:core:model</text>
</g>
<!-- :core:ui&#45;&gt;:core:model -->
<g id="edge3" class="edge">
<title>:core:ui&#45;&gt;:core:model</title>
<path fill="none" stroke="black" d="M256.83,-76.81C280.83,-66.04 316.19,-50.17 343.8,-37.78"/>
<polygon fill="black" stroke="black" points="344.89,-41.13 352.58,-33.84 342.02,-34.74 344.89,-41.13"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save