Merge branch 'main' into replace-testharness

pull/1230/head
Don Turner 3 months ago committed by GitHub
commit fc2146c970
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -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()
}
}
}

@ -2,70 +2,71 @@ 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.0
androidx.annotation:annotation:1.7.0
androidx.annotation:annotation-jvm:1.7.1
androidx.annotation:annotation:1.7.1
androidx.appcompat:appcompat-resources:1.6.1
androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0
androidx.browser:browser:1.6.0
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.1
androidx.compose.animation:animation-core-android:1.6.1
androidx.compose.animation:animation-core:1.6.1
androidx.compose.animation:animation:1.6.1
androidx.compose.foundation:foundation-android:1.6.1
androidx.compose.foundation:foundation-layout-android:1.6.1
androidx.compose.foundation:foundation-layout:1.6.1
androidx.compose.foundation:foundation:1.6.1
androidx.compose.material3:material3-android:1.2.0
androidx.compose.material3:material3:1.2.0
androidx.compose.material:material-icons-core-android:1.6.1
androidx.compose.material:material-icons-core:1.6.1
androidx.compose.material:material-icons-extended-android:1.6.1
androidx.compose.material:material-icons-extended:1.6.1
androidx.compose.material:material-ripple-android:1.6.1
androidx.compose.material:material-ripple:1.6.1
androidx.compose.runtime:runtime-android:1.6.1
androidx.compose.runtime:runtime-saveable-android:1.6.1
androidx.compose.runtime:runtime-saveable:1.6.1
androidx.compose.runtime:runtime:1.6.1
androidx.compose.ui:ui-android:1.6.1
androidx.compose.ui:ui-geometry-android:1.6.1
androidx.compose.ui:ui-geometry:1.6.1
androidx.compose.ui:ui-graphics-android:1.6.1
androidx.compose.ui:ui-graphics:1.6.1
androidx.compose.ui:ui-text-android:1.6.1
androidx.compose.ui:ui-text:1.6.1
androidx.compose.ui:ui-tooling-preview-android:1.6.1
androidx.compose.ui:ui-tooling-preview:1.6.1
androidx.compose.ui:ui-unit-android:1.6.1
androidx.compose.ui:ui-unit:1.6.1
androidx.compose.ui:ui-util-android:1.6.1
androidx.compose.ui:ui-util:1.6.1
androidx.compose.ui:ui:1.6.1
androidx.compose:compose-bom:2024.02.00
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.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:material3-android:1.2.1
androidx.compose.material3:material3:1.2.1
androidx.compose.material:material-icons-core-android:1.6.3
androidx.compose.material:material-icons-core:1.6.3
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: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:compose-bom:2024.02.02
androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.12.0
androidx.core:core:1.12.0
androidx.customview:customview-poolingcontainer:1.0.0
androidx.customview:customview:1.0.0
androidx.emoji2:emoji2:1.3.0
androidx.exifinterface:exifinterface:1.3.6
androidx.exifinterface:exifinterface:1.3.7
androidx.fragment:fragment:1.5.1
androidx.interpolator:interpolator:1.0.0
androidx.lifecycle:lifecycle-common-java8:2.6.2
androidx.lifecycle:lifecycle-common:2.6.2
androidx.lifecycle:lifecycle-livedata-core:2.6.2
androidx.lifecycle:lifecycle-livedata:2.6.2
androidx.lifecycle:lifecycle-process:2.6.2
androidx.lifecycle:lifecycle-runtime-ktx:2.6.2
androidx.lifecycle:lifecycle-runtime:2.6.2
androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2
androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.2
androidx.lifecycle:lifecycle-viewmodel:2.6.2
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-ktx:2.7.0
androidx.lifecycle:lifecycle-runtime: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.loader:loader:1.0.0
androidx.metrics:metrics-performance:1.0.0-alpha04
androidx.profileinstaller:profileinstaller:1.3.1
@ -86,16 +87,16 @@ com.google.dagger:hilt-android:2.51
com.google.dagger:hilt-core:2.51
com.google.guava:listenablefuture:1.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.6.0
com.squareup.okio:okio:3.6.0
io.coil-kt:coil-base:2.5.0
io.coil-kt:coil-compose-base:2.5.0
io.coil-kt:coil-compose:2.5.0
io.coil-kt:coil:2.5.0
com.squareup.okio:okio-jvm:3.8.0
com.squareup.okio:okio:3.8.0
io.coil-kt:coil-base:2.6.0
io.coil-kt:coil-compose-base:2.6.0
io.coil-kt:coil-compose:2.6.0
io.coil-kt:coil:2.6.0
javax.inject:javax.inject:1
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0
org.jetbrains.kotlin:kotlin-stdlib:1.9.22
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3

@ -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
}
@ -89,17 +88,23 @@ dependencies {
implementation(projects.sync.work)
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3.adaptive)
implementation(libs.androidx.compose.material3.adaptive.layout)
implementation(libs.androidx.compose.material3.adaptive.navigation)
implementation(libs.androidx.compose.material3.windowSizeClass)
implementation(libs.androidx.compose.runtime.tracing)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.core.splashscreen)
implementation(libs.androidx.tracing.ktx)
implementation(libs.androidx.hilt.navigation.compose)
implementation(libs.androidx.lifecycle.runtimeCompose)
implementation(libs.androidx.compose.runtime.tracing)
implementation(libs.androidx.compose.material3.windowSizeClass)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.profileinstaller)
implementation(libs.androidx.tracing.ktx)
implementation(libs.kotlinx.coroutines.guava)
implementation(libs.coil.kt)
ksp(libs.hilt.compiler)
debugImplementation(libs.androidx.compose.ui.testManifest)
debugImplementation(projects.uiTestHiltManifest)

@ -2,55 +2,61 @@ 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.0
androidx.annotation:annotation:1.7.0
androidx.annotation:annotation-jvm:1.7.1
androidx.annotation:annotation:1.7.1
androidx.appcompat:appcompat-resources:1.6.1
androidx.appcompat:appcompat:1.6.1
androidx.arch.core:core-common:2.2.0
androidx.arch.core:core-runtime:2.2.0
androidx.autofill:autofill:1.0.0
androidx.browser:browser:1.6.0
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.1
androidx.compose.animation:animation-core-android:1.6.1
androidx.compose.animation:animation-core:1.6.1
androidx.compose.animation:animation:1.6.1
androidx.compose.foundation:foundation-android:1.6.1
androidx.compose.foundation:foundation-layout-android:1.6.1
androidx.compose.foundation:foundation-layout:1.6.1
androidx.compose.foundation:foundation:1.6.1
androidx.compose.material3:material3-android:1.2.0
androidx.compose.material3:material3-window-size-class-android:1.2.0
androidx.compose.material3:material3-window-size-class:1.2.0
androidx.compose.material3:material3:1.2.0
androidx.compose.material:material-icons-core-android:1.6.1
androidx.compose.material:material-icons-core:1.6.1
androidx.compose.material:material-icons-extended-android:1.6.1
androidx.compose.material:material-icons-extended:1.6.1
androidx.compose.material:material-ripple-android:1.6.1
androidx.compose.material:material-ripple:1.6.1
androidx.compose.runtime:runtime-android:1.6.1
androidx.compose.runtime:runtime-saveable-android:1.6.1
androidx.compose.runtime:runtime-saveable:1.6.1
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.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: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
androidx.compose.material3:material3:1.2.1
androidx.compose.material:material-icons-core-android:1.6.3
androidx.compose.material:material-icons-core:1.6.3
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-tracing:1.0.0-beta01
androidx.compose.runtime:runtime:1.6.1
androidx.compose.ui:ui-android:1.6.1
androidx.compose.ui:ui-geometry-android:1.6.1
androidx.compose.ui:ui-geometry:1.6.1
androidx.compose.ui:ui-graphics-android:1.6.1
androidx.compose.ui:ui-graphics:1.6.1
androidx.compose.ui:ui-text-android:1.6.1
androidx.compose.ui:ui-text:1.6.1
androidx.compose.ui:ui-tooling-preview-android:1.6.1
androidx.compose.ui:ui-tooling-preview:1.6.1
androidx.compose.ui:ui-unit-android:1.6.1
androidx.compose.ui:ui-unit:1.6.1
androidx.compose.ui:ui-util-android:1.6.1
androidx.compose.ui:ui-util:1.6.1
androidx.compose.ui:ui:1.6.1
androidx.compose:compose-bom:2024.02.00
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:compose-bom:2024.02.02
androidx.concurrent:concurrent-futures:1.1.0
androidx.core:core-ktx:1.12.0
androidx.core:core-splashscreen:1.0.1
@ -66,11 +72,11 @@ androidx.documentfile:documentfile:1.0.0
androidx.drawerlayout:drawerlayout:1.0.0
androidx.emoji2:emoji2-views-helper:1.3.0
androidx.emoji2:emoji2:1.3.0
androidx.exifinterface:exifinterface:1.3.6
androidx.exifinterface:exifinterface:1.3.7
androidx.fragment:fragment:1.5.1
androidx.hilt:hilt-common:1.1.0
androidx.hilt:hilt-navigation-compose:1.0.0
androidx.hilt:hilt-navigation:1.0.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
@ -116,12 +122,15 @@ androidx.vectordrawable:vectordrawable-animated:1.1.0
androidx.vectordrawable:vectordrawable:1.1.0
androidx.versionedparcelable:versionedparcelable:1.1.1
androidx.viewpager:viewpager:1.0.0
androidx.window:window: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.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
@ -178,33 +187,31 @@ com.google.protobuf:protobuf-kotlin-lite:3.25.2
com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0
com.squareup.okhttp3:logging-interceptor:4.12.0
com.squareup.okhttp3:okhttp:4.12.0
com.squareup.okio:okio-jvm:3.6.0
com.squareup.okio:okio:3.6.0
com.squareup.okio:okio-jvm:3.8.0
com.squareup.okio:okio:3.8.0
com.squareup.retrofit2:retrofit:2.9.0
io.coil-kt:coil-base:2.5.0
io.coil-kt:coil-compose-base:2.5.0
io.coil-kt:coil-compose:2.5.0
io.coil-kt:coil-svg:2.5.0
io.coil-kt:coil:2.5.0
io.github.aakira:napier-android:1.4.1
io.github.aakira:napier:1.4.1
io.coil-kt:coil-base:2.6.0
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
javax.inject:javax.inject:1
org.checkerframework:checker-qual:3.12.0
org.jetbrains.kotlin:kotlin-stdlib-common:1.9.22
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.10
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.10
org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.9.0
org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.9.0
org.jetbrains.kotlin:kotlin-stdlib:1.9.22
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.7.3
org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.8.0
org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.8.0
org.jetbrains.kotlinx:kotlinx-datetime-jvm:0.5.0
org.jetbrains.kotlinx:kotlinx-datetime:0.5.0
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0
org.jetbrains.kotlinx:kotlinx-serialization-bom:1.6.3
org.jetbrains.kotlinx:kotlinx-serialization-core-jvm:1.6.3
org.jetbrains.kotlinx:kotlinx-serialization-core:1.6.3
org.jetbrains.kotlinx:kotlinx-serialization-json-jvm:1.6.3
org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3
org.jetbrains:annotations:23.0.0

@ -274,10 +274,10 @@ class NavigationTest {
// Select the last topic
val topic = runBlocking {
topicsRepository.getTopics().first().sortedBy(Topic::name).last().name
topicsRepository.getTopics().first().sortedBy(Topic::name).last()
}
onNodeWithTag("interests:topics").performScrollToNode(hasText(topic))
onNodeWithText(topic).performClick()
onNodeWithTag("interests:topics").performScrollToNode(hasText(topic.name))
onNodeWithText(topic.name).performClick()
// Switch tab
onNodeWithText(forYou).performClick()
@ -285,8 +285,8 @@ class NavigationTest {
// Come back to Interests
onNodeWithText(interests).performClick()
// Verify we're not in the list of interests
onNodeWithTag("interests:topics").assertDoesNotExist()
// Verify the topic is still shown
onNodeWithTag("topic:${topic.id}").assertExists()
}
}
}

@ -167,7 +167,7 @@ class NiaAppStateTest {
}
@Test
fun stateIsOfflineWhenNetworkMonitorIsOffline() = runTest(UnconfinedTestDispatcher()) {
fun niaAppState_whenNetworkMonitorIsOffline_StateIsOffline() = runTest(UnconfinedTestDispatcher()) {
composeTestRule.setContent {
state = NiaAppState(
navController = NavHostController(LocalContext.current),
@ -214,7 +214,7 @@ class NiaAppStateTest {
@Composable
private fun rememberTestNavController(): TestNavHostController {
val context = LocalContext.current
return remember<TestNavHostController> {
return remember {
TestNavHostController(context).apply {
navigatorProvider.addNavigator(ComposeNavigator())
graph = createGraph(startDestination = "a") {

@ -22,12 +22,11 @@ import androidx.navigation.compose.NavHost
import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.bookmarksScreen
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.FOR_YOU_ROUTE
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.forYouScreen
import com.google.samples.apps.nowinandroid.feature.interests.navigation.interestsGraph
import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterests
import com.google.samples.apps.nowinandroid.feature.search.navigation.searchScreen
import com.google.samples.apps.nowinandroid.feature.topic.navigation.navigateToTopic
import com.google.samples.apps.nowinandroid.feature.topic.navigation.topicScreen
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.INTERESTS
import com.google.samples.apps.nowinandroid.ui.NiaAppState
import com.google.samples.apps.nowinandroid.ui.interests2pane.interestsListDetailScreen
/**
* Top-level navigation graph. Navigation is organized as explained at
@ -49,24 +48,16 @@ fun NiaNavHost(
startDestination = startDestination,
modifier = modifier,
) {
forYouScreen(onTopicClick = navController::navigateToTopic)
forYouScreen(onTopicClick = navController::navigateToInterests)
bookmarksScreen(
onTopicClick = navController::navigateToTopic,
onTopicClick = navController::navigateToInterests,
onShowSnackbar = onShowSnackbar,
)
searchScreen(
onBackClick = navController::popBackStack,
onInterestsClick = { appState.navigateToTopLevelDestination(INTERESTS) },
onTopicClick = navController::navigateToTopic,
)
interestsGraph(
onTopicClick = navController::navigateToTopic,
nestedGraphs = {
topicScreen(
onBackClick = navController::popBackStack,
onTopicClick = navController::navigateToTopic,
)
},
onTopicClick = navController::navigateToInterests,
)
interestsListDetailScreen()
}
}

@ -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
@ -78,7 +77,6 @@ import com.google.samples.apps.nowinandroid.feature.settings.R as settingsR
@OptIn(
ExperimentalMaterial3Api::class,
ExperimentalLayoutApi::class,
ExperimentalComposeUiApi::class,
)
@Composable

@ -39,7 +39,7 @@ import com.google.samples.apps.nowinandroid.feature.bookmarks.navigation.navigat
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.FOR_YOU_ROUTE
import com.google.samples.apps.nowinandroid.feature.foryou.navigation.navigateToForYou
import com.google.samples.apps.nowinandroid.feature.interests.navigation.INTERESTS_ROUTE
import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterestsGraph
import com.google.samples.apps.nowinandroid.feature.interests.navigation.navigateToInterests
import com.google.samples.apps.nowinandroid.feature.search.navigation.navigateToSearch
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination
import com.google.samples.apps.nowinandroid.navigation.TopLevelDestination.BOOKMARKS
@ -173,7 +173,7 @@ class NiaAppState(
when (topLevelDestination) {
FOR_YOU -> navController.navigateToForYou(topLevelNavOptions)
BOOKMARKS -> navController.navigateToBookmarks(topLevelNavOptions)
INTERESTS -> navController.navigateToInterestsGraph(topLevelNavOptions)
INTERESTS -> navController.navigateToInterests(null, topLevelNavOptions)
}
}
}

@ -0,0 +1,35 @@
/*
* 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.
* 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.interests2pane
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import com.google.samples.apps.nowinandroid.feature.interests.navigation.TOPIC_ID_ARG
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.StateFlow
import javax.inject.Inject
@HiltViewModel
class Interests2PaneViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
) : ViewModel() {
val selectedTopicId: StateFlow<String?> = savedStateHandle.getStateFlow(TOPIC_ID_ARG, null)
fun onTopicClick(topicId: String?) {
savedStateHandle[TOPIC_ID_ARG] = topicId
}
}

@ -0,0 +1,134 @@
/*
* 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.
* 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.interests2pane
import androidx.activity.compose.BackHandler
import androidx.compose.material3.adaptive.ExperimentalMaterial3AdaptiveApi
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffold
import androidx.compose.material3.adaptive.layout.ListDetailPaneScaffoldRole
import androidx.compose.material3.adaptive.layout.PaneAdaptedValue
import androidx.compose.material3.adaptive.navigation.ThreePaneScaffoldNavigator
import androidx.compose.material3.adaptive.navigation.rememberListDetailPaneScaffoldNavigator
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavGraphBuilder
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
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
private const val DETAIL_PANE_NAVHOST_ROUTE = "detail_pane_route"
fun NavGraphBuilder.interestsListDetailScreen() {
composable(
route = INTERESTS_ROUTE,
arguments = listOf(
navArgument(TOPIC_ID_ARG) {
type = NavType.StringType
defaultValue = null
nullable = true
},
),
) {
InterestsListDetailScreen()
}
}
@Composable
internal fun InterestsListDetailScreen(
viewModel: Interests2PaneViewModel = hiltViewModel(),
) {
val selectedTopicId by viewModel.selectedTopicId.collectAsStateWithLifecycle()
InterestsListDetailScreen(
selectedTopicId = selectedTopicId,
onTopicClick = viewModel::onTopicClick,
)
}
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
@Composable
internal fun InterestsListDetailScreen(
selectedTopicId: String?,
onTopicClick: (String) -> Unit,
) {
val listDetailNavigator = rememberListDetailPaneScaffoldNavigator<Nothing>()
BackHandler(listDetailNavigator.canNavigateBack()) {
listDetailNavigator.navigateBack()
}
val nestedNavController = rememberNavController()
fun onTopicClickShowDetailPane(topicId: String) {
onTopicClick(topicId)
nestedNavController.navigateToTopic(topicId) {
popUpTo(DETAIL_PANE_NAVHOST_ROUTE)
}
listDetailNavigator.navigateTo(ListDetailPaneScaffoldRole.Detail)
}
ListDetailPaneScaffold(
value = listDetailNavigator.scaffoldValue,
directive = listDetailNavigator.scaffoldDirective,
listPane = {
InterestsRoute(
onTopicClick = ::onTopicClickShowDetailPane,
highlightSelectedTopic = listDetailNavigator.isDetailPaneVisible(),
)
},
detailPane = {
NavHost(
navController = nestedNavController,
startDestination = TOPIC_ROUTE,
route = DETAIL_PANE_NAVHOST_ROUTE,
) {
topicScreen(
showBackButton = !listDetailNavigator.isListPaneVisible(),
onBackClick = listDetailNavigator::navigateBack,
onTopicClick = ::onTopicClickShowDetailPane,
)
composable(route = TOPIC_ROUTE) {
TopicDetailPlaceholder()
}
}
},
)
LaunchedEffect(Unit) {
if (selectedTopicId != null) {
// Initial topic ID was provided when navigating to Interests, so show its details.
onTopicClickShowDetailPane(selectedTopicId)
}
}
}
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
private fun <T> ThreePaneScaffoldNavigator<T>.isListPaneVisible(): Boolean =
scaffoldValue[ListDetailPaneScaffoldRole.List] == PaneAdaptedValue.Expanded
@OptIn(ExperimentalMaterial3AdaptiveApi::class)
private fun <T> ThreePaneScaffoldNavigator<T>.isDetailPaneVisible(): Boolean =
scaffoldValue[ListDetailPaneScaffoldRole.Detail] == PaneAdaptedValue.Expanded

@ -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
@ -41,7 +41,7 @@ import javax.inject.Inject
*/
internal 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(

@ -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)

@ -27,6 +27,7 @@ import com.google.samples.apps.nowinandroid.core.database.model.asExternalModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.test.runTest
import kotlinx.datetime.Instant
import org.junit.After
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
@ -48,6 +49,9 @@ class NewsResourceDaoTest {
topicDao = db.topicDao()
}
@After
fun closeDb() = db.close()
@Test
fun newsResourceDao_fetches_items_by_descending_publish_date() = runTest {
val newsResourceEntities = listOf(

@ -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,

@ -48,7 +48,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>()

@ -47,7 +47,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>()

@ -45,7 +45,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>()

@ -42,7 +42,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
@ -30,6 +30,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.selected
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@ -37,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(
@ -49,6 +50,7 @@ fun InterestsItem(
modifier: Modifier = Modifier,
iconModifier: Modifier = Modifier,
description: String = "",
isSelected: Boolean = false,
) {
ListItem(
leadingContent = {
@ -68,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,
),
)
},
@ -76,17 +78,23 @@ 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,
),
)
},
)
},
colors = ListItemDefaults.colors(
containerColor = Color.Transparent,
containerColor = if (isSelected) {
MaterialTheme.colorScheme.surfaceVariant
} else {
Color.Transparent
},
),
modifier = modifier
.semantics(mergeDescendants = true) { /* no-op */ }
.semantics(mergeDescendants = true) {
selected = isSelected
}
.clickable(enabled = true, onClick = onClick),
)
}
@ -179,3 +187,21 @@ private fun InterestsCardWithEmptyDescriptionPreview() {
}
}
}
@Preview
@Composable
private fun InterestsCardSelectedPreview() {
NiaTheme {
Surface {
InterestsItem(
name = "Compose",
description = "",
following = true,
topicImageUrl = "",
onClick = { },
onFollowButtonClick = { },
isSelected = true,
)
}
}
}

@ -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

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

Loading…
Cancel
Save