Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ jobs:
run: >
./gradlew
:agent-sdk:testDebugUnitTest
:app-sdk:testDebugUnitTest
:mock-aura-data:testDebugUnitTest
:mock-harmony-mesh:testDebugUnitTest
:mock-self-wallet:testDebugUnitTest
:mock-app-store:testDebugUnitTest
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ jobs:
run: >
./gradlew
:agent-sdk:testDebugUnitTest
:app-sdk:testDebugUnitTest
:mock-aura-data:testDebugUnitTest
:mock-harmony-mesh:testDebugUnitTest
:mock-self-wallet:testDebugUnitTest
:mock-app-store:testDebugUnitTest
Expand Down
25 changes: 25 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,34 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version

## [Unreleased]

## [0.1.2-community] - 2026-07-02

### Added

- Unit tests for **DefaultPluginRegistry** (`app-sdk`) and **MockAuraDataClient** (`mock-aura-data`).
- **DefaultAgentRuntime** test coverage for agents that throw during `sendRequest`.
- Shared **UI test tags** (`UiTestTags`) on Android and iOS home screen, welcome text, and home tab.
- **Settings** screen shows app version (Android `BuildConfig`, iOS bundle short version).
- CI runs `:app-sdk:testDebugUnitTest` and `:mock-aura-data:testDebugUnitTest`.

### Changed

- Launch smoke tests assert stable `home_screen` / `home_welcome` identifiers instead of brittle text matching.
- Android `versionName` **0.1.2-community** (`versionCode` 2); iOS `MARKETING_VERSION` **0.1.2**.

### Fixed

- **DefaultPluginRegistry** stops the previous app when re-registering the same id.

## [0.1.1-community] - 2026-07-02

### Added

- Android **launch smoke test** (`LaunchSmokeTest`) and iOS **UI launch test** (`SelfOSPIUITests`).
- GitHub Release workflow now attaches **Android APKs** (debug + release R8 builds).
- [DEVELOPMENT.md](DEVELOPMENT.md) — local build, test, and first-agent contributor guide.
- [SECURITY.md](SECURITY.md) — private vulnerability reporting and disclosure timelines.
- Optional **android-smoke** CI job (emulator launch test; non-blocking).

## [0.1.0-community] - 2026-07-02

Expand All @@ -31,4 +53,7 @@ Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/). Version

- `scripts/scan-public-safety` blocks risky filenames and patterns before release.

[Unreleased]: https://github.com/selflabs/SELF-OS/compare/v0.1.2-community...HEAD
[0.1.2-community]: https://github.com/selflabs/SELF-OS/compare/v0.1.1-community...v0.1.2-community
[0.1.1-community]: https://github.com/selflabs/SELF-OS/compare/v0.1.0-community...v0.1.1-community
[0.1.0-community]: https://github.com/selflabs/SELF-OS/releases/tag/v0.1.0-community
16 changes: 10 additions & 6 deletions apps/self-shell-ios/SelfOSPI.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
A1000001000000000000000F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A2000001000000000000000F /* Assets.xcassets */; };
A10000010000000000000010 /* SelfOSPITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A20000010000000000000010 /* SelfOSPITests.swift */; };
A10000010000000000000011 /* SelfOSPIUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A20000010000000000000011 /* SelfOSPIUITests.swift */; };
A10000010000000000000012 /* UiTestTags.swift in Sources */ = {isa = PBXBuildFile; fileRef = A20000010000000000000012 /* UiTestTags.swift */; };
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
Expand All @@ -47,6 +48,7 @@
A30000010000000000000003 /* SelfOSPIUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SelfOSPIUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
A20000010000000000000010 /* SelfOSPITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfOSPITests.swift; sourceTree = "<group>"; };
A20000010000000000000011 /* SelfOSPIUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelfOSPIUITests.swift; sourceTree = "<group>"; };
A20000010000000000000012 /* UiTestTags.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UiTestTags.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -89,6 +91,7 @@
children = (
A20000010000000000000001 /* SelfOSPIApp.swift */,
A20000010000000000000002 /* ContentView.swift */,
A20000010000000000000012 /* UiTestTags.swift */,
A20000010000000000000003 /* Models.swift */,
A50000010000000000000004 /* Agents */,
A50000010000000000000005 /* Services */,
Expand Down Expand Up @@ -346,6 +349,7 @@
buildActionMask = 2147483647;
files = (
A10000010000000000000001 /* SelfOSPIApp.swift in Sources */,
A10000010000000000000012 /* UiTestTags.swift in Sources */,
A10000010000000000000002 /* ContentView.swift in Sources */,
A10000010000000000000003 /* Models.swift in Sources */,
A10000010000000000000004 /* ExampleAgents.swift in Sources */,
Expand Down Expand Up @@ -428,7 +432,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1.2;
PRODUCT_BUNDLE_IDENTIFIER = ai.selflabs.selfospi;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand Down Expand Up @@ -457,7 +461,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1.2;
PRODUCT_BUNDLE_IDENTIFIER = ai.selflabs.selfospi;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
Expand All @@ -480,7 +484,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1.2;
PRODUCT_BUNDLE_IDENTIFIER = ai.selflabs.selfospi.tests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
Expand All @@ -504,7 +508,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1.2;
PRODUCT_BUNDLE_IDENTIFIER = ai.selflabs.selfospi.tests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
Expand All @@ -527,7 +531,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1.2;
PRODUCT_BUNDLE_IDENTIFIER = ai.selflabs.selfospi.uitests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
Expand All @@ -550,7 +554,7 @@
"@executable_path/Frameworks",
"@loader_path/Frameworks",
);
MARKETING_VERSION = 1.0;
MARKETING_VERSION = 0.1.2;
PRODUCT_BUNDLE_IDENTIFIER = ai.selflabs.selfospi.uitests;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = NO;
Expand Down
1 change: 1 addition & 0 deletions apps/self-shell-ios/SelfOSPI/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ struct ContentView: View {
TabView {
HomeView()
.tabItem { Label("Home", systemImage: "house") }
.accessibilityIdentifier(UiTestTags.tabHome)
AgentsView()
.tabItem { Label("Agents", systemImage: "cpu") }
AppsView()
Expand Down
5 changes: 5 additions & 0 deletions apps/self-shell-ios/SelfOSPI/UiTestTags.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
enum UiTestTags {
static let homeScreen = "home_screen"
static let homeWelcome = "home_welcome"
static let tabHome = "tab_home"
}
2 changes: 2 additions & 0 deletions apps/self-shell-ios/SelfOSPI/Views/HomeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ struct HomeView: View {
Text("SELF OS Personal Intelligence (Community Edition)")
.font(.title2.bold())
Text("Welcome to your local personal intelligence workspace.")
.accessibilityIdentifier(UiTestTags.homeWelcome)
MockPreviewBanner()
card("Local agents") { Text("\(env.agentRuntime.manifests.count) example agents registered.") }
card("Installed SELF Apps") { Text("\(env.selfApps.count) demo apps in the local registry.") }
Expand All @@ -25,6 +26,7 @@ struct HomeView: View {
}
.padding()
}
.accessibilityIdentifier(UiTestTags.homeScreen)
.navigationTitle("Home")
}
}
Expand Down
1 change: 1 addition & 0 deletions apps/self-shell-ios/SelfOSPI/Views/SettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ struct SettingsView: View {
.pickerStyle(.segmented)
}
Section("About") {
Text("Version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "—")")
Text("SELF OS Personal Intelligence keeps agents and mock services on-device. See docs/OPEN_SOURCE_BOUNDARY.md in the repository.")
.font(.caption)
}
Expand Down
5 changes: 2 additions & 3 deletions apps/self-shell-ios/SelfOSPIUITests/SelfOSPIUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ final class LaunchSmokeTests: XCTestCase {
let app = XCUIApplication()
app.launch()

let headline = app.staticTexts["SELF OS Personal Intelligence (Community Edition)"]
XCTAssertTrue(headline.waitForExistence(timeout: 10))

XCTAssertTrue(app.descendants(matching: .any)["home_screen"].waitForExistence(timeout: 10))
XCTAssertTrue(app.descendants(matching: .any)["home_welcome"].exists)
XCTAssertTrue(app.tabBars.buttons["Home"].exists)
}
}
5 changes: 3 additions & 2 deletions apps/self-shell/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ android {
applicationId = "ai.selflabs.selfshell"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "0.1.0-community"
versionCode = 2
versionName = "0.1.2-community"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

Expand Down Expand Up @@ -42,6 +42,7 @@ android {

buildFeatures {
compose = true
buildConfig = true
}

composeOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ package ai.selflabs.selfshell

import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.junit4.createAndroidComposeRule
import androidx.compose.ui.test.onAllNodesWithText
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onNodeWithTag
import ai.selflabs.selfshell.ui.UiTestTags
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
Expand All @@ -19,12 +20,12 @@ class LaunchSmokeTest {
fun launchesHomeScreen() {
composeTestRule.waitUntil(timeoutMillis = 15_000) {
composeTestRule
.onAllNodesWithText("Welcome to your local personal intelligence shell.", useUnmergedTree = true)
.onAllNodesWithTag(UiTestTags.HOME_SCREEN, useUnmergedTree = true)
.fetchSemanticsNodes()
.isNotEmpty()
}
composeTestRule
.onNodeWithText("Welcome to your local personal intelligence shell.")
.assertIsDisplayed()
composeTestRule.onNodeWithTag(UiTestTags.HOME_SCREEN).assertIsDisplayed()
composeTestRule.onNodeWithTag(UiTestTags.HOME_WELCOME).assertIsDisplayed()
composeTestRule.onNodeWithTag(UiTestTags.TAB_HOME).assertIsDisplayed()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
Expand All @@ -31,6 +32,7 @@ import ai.selflabs.selfshell.ui.home.HomeScreen
import ai.selflabs.selfshell.ui.mesh.MeshPreviewScreen
import ai.selflabs.selfshell.ui.settings.SettingsScreen
import ai.selflabs.selfshell.ui.theme.SelfShellTheme
import ai.selflabs.selfshell.ui.UiTestTags
import ai.selflabs.selfshell.ui.wallet.WalletPreviewScreen

class MainActivity : ComponentActivity() {
Expand Down Expand Up @@ -62,7 +64,8 @@ class MainActivity : ComponentActivity() {
}
},
icon = { Icon(Icons.Default.Home, contentDescription = null) },
label = { Text("Home") }
label = { Text("Home") },
modifier = Modifier.testTag(UiTestTags.TAB_HOME)
)
NavigationBarItem(
selected = route == Routes.Agents,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ai.selflabs.selfshell.ui

object UiTestTags {
const val HOME_SCREEN = "home_screen"
const val HOME_WELCOME = "home_welcome"
const val TAB_HOME = "tab_home"
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.dp
import ai.selflabs.selfshell.SelfShellApplication
import ai.selflabs.selfshell.mock.harmony.MeshNodeStatus
import ai.selflabs.selfshell.mock.wallet.MockBalance
import ai.selflabs.selfshell.ui.components.MockPreviewBanner
import ai.selflabs.selfshell.ui.components.PreviewDisclaimer
import ai.selflabs.selfshell.ui.UiTestTags
@Composable
fun HomeScreen(app: SelfShellApplication) {
var node by remember { mutableStateOf<MeshNodeStatus?>(null) }
Expand All @@ -37,6 +39,7 @@ fun HomeScreen(app: SelfShellApplication) {
Column(
modifier = Modifier
.fillMaxSize()
.testTag(UiTestTags.HOME_SCREEN)
.verticalScroll(rememberScrollState())
.padding(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
Expand All @@ -47,7 +50,8 @@ fun HomeScreen(app: SelfShellApplication) {
)
Text(
text = "Welcome to your local personal intelligence shell.",
style = MaterialTheme.typography.bodyMedium
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier.testTag(UiTestTags.HOME_WELCOME)
)
MockPreviewBanner()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ai.selflabs.selfshell.ui.settings

import ai.selflabs.selfshell.SelfShellApplication
import ai.selflabs.selfshell.BuildConfig
import ai.selflabs.selfshell.ThemeChoice
import ai.selflabs.selfshell.mock.aura.MockDataCategory
import androidx.compose.foundation.layout.Arrangement
Expand Down Expand Up @@ -58,6 +59,11 @@ fun SettingsScreen(app: SelfShellApplication) {
) {
Text("Settings", style = MaterialTheme.typography.headlineSmall)

Text(
text = "Version ${BuildConfig.VERSION_NAME}",
style = MaterialTheme.typography.bodyMedium
)

RowSwitch("Local-first mode (recommended)", localMode) { localMode = it }
RowSwitch("Developer mode", devMode) { devMode = it }

Expand Down
3 changes: 3 additions & 0 deletions mock/aura-data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,7 @@ android {

dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")

testImplementation("junit:junit:4.13.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package ai.selflabs.selfshell.mock.aura

import kotlinx.coroutines.test.runTest
import org.junit.Assert.assertEquals
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Test

class MockAuraDataClientTest {

private val client = MockAuraDataClient()

@Test
fun listMockDataCategories_returnsEducationalSamples() = runTest {
val categories = client.listMockDataCategories()

assertEquals(2, categories.size)
assertTrue(categories.any { it.id == "cat-usage" })
assertTrue(categories.any { it.id == "cat-diagnostics" })
}

@Test
fun updateConsentPreference_persistsInSnapshot() = runTest {
client.updateConsentPreference(ConsentPreference("cat-usage", true))

val snapshot = client.snapshotConsent()
assertTrue(snapshot["cat-usage"] == true)
assertFalse(snapshot["cat-diagnostics"] == true)
}

@Test
fun estimateMockDataValue_returnsPreviewOnlyCopy() = runTest {
val estimate = client.estimateMockDataValue("cat-usage")

assertEquals("cat-usage", estimate.categoryId)
assertTrue(estimate.displayValue.contains("mock", ignoreCase = true))
assertTrue(estimate.note.contains("Preview only", ignoreCase = true))
}

@Test
fun snapshotConsent_startsWithDefaultsOff() {
val snapshot = client.snapshotConsent()

assertEquals(false, snapshot["cat-usage"])
assertEquals(false, snapshot["cat-diagnostics"])
}
}
Loading
Loading