From 17b8bdaa15cf3636566cea949ae2f87d1eaf8162 Mon Sep 17 00:00:00 2001 From: denwritescode Date: Thu, 9 Jul 2026 16:01:00 +0300 Subject: [PATCH 1/8] + fixing gitignore --- .gitignore | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.gitignore b/.gitignore index a75e9cc19..2307455db 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,11 @@ bin/ ### Mac OS ### .DS_Store +### Xcode (per-user, machine-specific — should never be committed) ### +**/xcuserdata/ +*.xcuserstate +DerivedData/ + ### Kotlin ### .kotlin/ From 47b14b1e16ea03daa69ad2de7cdb3b9fe25ad5a2 Mon Sep 17 00:00:00 2001 From: denwritescode Date: Thu, 9 Jul 2026 16:07:12 +0300 Subject: [PATCH 2/8] + quick-start docs --- .gitignore | 1 + .../composeApp/src/debug/AndroidManifest.xml | 6 + localQuickStart/README.md | 145 ++++++++++++++++++ localQuickStart/run-backend-local.sh | 83 ++++++++++ localQuickStart/seed-local-db.sh | 57 +++++++ 5 files changed, 292 insertions(+) create mode 100644 clients/tablet/composeApp/src/debug/AndroidManifest.xml create mode 100644 localQuickStart/README.md create mode 100755 localQuickStart/run-backend-local.sh create mode 100755 localQuickStart/seed-local-db.sh diff --git a/.gitignore b/.gitignore index 2307455db..321e29928 100644 --- a/.gitignore +++ b/.gitignore @@ -86,6 +86,7 @@ coverage/ /oldProject/ /clients/tablet/composeApp/google-services.json /local.properties +/localQuickStart/.local-fake-credentials.json /deploy/dev/.env /deploy/dev/*.jar /deploy/prod/.env diff --git a/clients/tablet/composeApp/src/debug/AndroidManifest.xml b/clients/tablet/composeApp/src/debug/AndroidManifest.xml new file mode 100644 index 000000000..dc566c6d5 --- /dev/null +++ b/clients/tablet/composeApp/src/debug/AndroidManifest.xml @@ -0,0 +1,6 @@ + + + + + diff --git a/localQuickStart/README.md b/localQuickStart/README.md new file mode 100644 index 000000000..35a333d6e --- /dev/null +++ b/localQuickStart/README.md @@ -0,0 +1,145 @@ +# localQuickStart — run Effective-Office locally, fully offline + +This folder gets the **backend + the Meeting Room tablet app** running on your machine +**without any external accounts** (no Google / Firebase / Synology / Notion / Clockify / +Mattermost). It's meant for "just show me how it works" demos and local development. + +> The official README "run locally" path assumes you have **real** Google & Firebase +> service accounts (it even asks you to drop in `google-credentials.json` / +> `firebase-credentials.json`). Without them the server won't even boot. This quick-start +> swaps every provider for the project's built-in **dummy** implementations instead. + +--- + +## What's in this folder + +| File | Purpose | +|------|---------| +| `run-backend-local.sh` | Starts the Spring Boot backend with all providers set to dummy and every required env var filled with safe dummy values. | +| `seed-local-db.sh` | Inserts an API key + one zone + two meeting rooms (`Sync`, `Focus`) so the tablet has something to show. Idempotent. | +| `.local-fake-credentials.json` | A **self-generated, fake** Google service-account JSON (valid RSA key, never used to talk to Google). Auto-created by `run-backend-local.sh` if missing. **Git-ignored** (contains a private key → would trip the Gitleaks pre-commit hook). | + +## Prerequisites + +- Docker (for Postgres) +- JDK 17+ (JDK 21 works — the build targets bytecode 17, no strict toolchain) +- To run the client: **Android SDK + an emulator**, or **Xcode** (iOS simulator) + +--- + +## 1. Start Postgres + +```bash +docker run --name postgres-effectiveoffice \ + -e POSTGRES_DB=effectiveoffice -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres \ + -p 5432:5432 -d postgres:15-alpine +``` + +## 2. Start the backend + +```bash +localQuickStart/run-backend-local.sh +``` + +Wait for `Started EffectiveOfficeApplicationKt`. It listens on **http://localhost:8080**, +context path **`/api`**. Quick check (Swagger UI is public and should return 200): + +```bash +curl -o /dev/null -w '%{http_code}\n' http://localhost:8080/api/swagger-ui/index.html # -> 200 +open http://localhost:8080/api/swagger-ui/index.html +``` + +> Note: `/api/actuator/health` returns **403** here — actuator sits behind its own security in +> this project, so don't use it as a liveness check. Use the Swagger URL or the authorized +> call in step 3 instead. + +## 3. Seed the database + +```bash +localQuickStart/seed-local-db.sh +``` + +Verify auth + data (the key matches `apiKey` in the repo-root `local.properties`): + +```bash +curl -H "Authorization: Bearer effective-office-local-key" \ + "http://localhost:8080/api/v1/workspaces?workspace_tag=meeting" +# -> JSON array with "Sync" and "Focus" +``` + +## 4. Run the tablet app + +The client reads its config from the **repo-root `local.properties`** +(`api.url.debug`, `api.url.release`, `apiKey`, `sdk.dir`). + +⚠️ **The base URL is baked into the build**, and it differs per platform: + +| Target | `api.url.debug` | +|--------|-----------------| +| **iOS simulator** (shares the host network) | `http://localhost:8080` | +| **Android emulator** (host is reachable via a special IP) | `http://10.0.2.2:8080` | + +Change that one line and rebuild when you switch platforms. + +### Android emulator + +```bash +# any tablet-sized AVD; build, install, launch: +./gradlew :clients:tablet:composeApp:assembleDebug +adb install -r -g clients/tablet/composeApp/build/outputs/apk/debug/composeApp-debug.apk +adb shell monkey -p band.effective.office.tablet -c android.intent.category.LAUNCHER 1 +``` + +### iOS simulator + +Open `iosApp/iosApp.xcodeproj` in Xcode and hit **Run** (scheme `iosApp`), or: + +```bash +xcodebuild -project iosApp/iosApp.xcodeproj -scheme iosApp -sdk iphonesimulator \ + -destination 'platform=iOS Simulator,name=iPad Pro 11-inch (M4)' build +``` + +To iterate on Kotlin/Native compile errors fast (without Xcode): +`./gradlew :clients:tablet:composeApp:linkDebugFrameworkIosSimulatorArm64` + +--- + +## Files this quick-start needs elsewhere in the repo + +These can't live in this folder — the build looks for them at fixed locations. They're all +**git-ignored** and safe to keep locally: + +| Path | Why | +|------|-----| +| `local.properties` (repo root) | `api.url.debug/release`, `apiKey`, `sdk.dir`. Any Gradle build fails without it (settings.gradle configures every module). | +| `clients/tablet/composeApp/google-services.json` | The `com.google.gms.google-services` plugin refuses to build without it. A dummy file with package `band.effective.office.tablet` is enough. | +| `clients/tablet/composeApp/src/debug/AndroidManifest.xml` | Debug-only overlay adding `usesCleartextTraffic="true"` so the debug build can reach the local **http** backend. | +| `keystore/debug.keystore` | Debug signing config (alias `androiddebugkey`, store/key pass `android`). Generate with `keytool`. | + +--- + +## Why the workarounds exist (project quirks) + +**Backend** +- Provider config classes (Synology, Mattermost, …) load **unconditionally** and read env + with self-referential YAML (`SYNOLOGY_IP: ${SYNOLOGY_IP:}`) — so every such var must be + non-empty or Spring dies with a "circular placeholder reference". Hence the long dummy list. +- `photo.saver.provider=dummy` clashes with the photos dummy (both register a bean named + `dummyPhotoProvider`) → we use `mattermost` with the scheduler disabled instead. +- `FirebaseConfig` reads the credentials file at startup no matter what → the fake JSON. + +**iOS** (the `iosMain` source set was clearly never built in CI — it needed 5 fixes to compile): +1. `clients/shared/core/.../DateTimeUtils.ios.kt` — dropped `NSLocale.currentLocale` + (unresolved on the current Kotlin/Native; `NSDateFormatter` defaults to the current locale). +2. Deleted orphaned `clients/tablet/core/data/src/iosMain/.../HttpClientFactory.ios.kt` + (`actual` with no `expect`). +3. `clients/tablet/core/ui/.../DateFormatter.ios.kt` — added `import platform.Foundation.languageCode`. +4. `clients/tablet/feature/main/build.gradle.kts` — hardcoded `components-ui-tooling-preview:1.10.0` + pulled a Kotlin 2.2.x klib (project is 2.1.21) → switched to the managed `compose.components.uiToolingPreview`. +5. Stripped `widthDp/heightDp/locale` params from `@Preview` in feature:main (those params only + exist in the newer Preview annotation; Compose here is 1.8.1). + +## Limitations + +- Providers are dummy → bookings live in-memory in the backend and are **not** synced to a real + Google Calendar. Perfect for demos, not for real scheduling. diff --git a/localQuickStart/run-backend-local.sh b/localQuickStart/run-backend-local.sh new file mode 100755 index 000000000..fa937912a --- /dev/null +++ b/localQuickStart/run-backend-local.sh @@ -0,0 +1,83 @@ +#!/usr/bin/env bash +# Local, fully-offline backend run for Effective-Office. +# +# All external providers are switched to dummy/in-memory so NO real +# Google / Firebase / Synology / Notion / Clockify / Mattermost accounts are needed. +# See ./README.md in this folder for the full story and the client steps. +# +# Prereqs: Postgres running on localhost:5432 (db=effectiveoffice, user/pass=postgres): +# docker run --name postgres-effectiveoffice -e POSTGRES_DB=effectiveoffice \ +# -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres -p 5432:5432 -d postgres:15-alpine +# +# Usage (from anywhere): +# localQuickStart/run-backend-local.sh +set -euo pipefail + +# This script lives in /localQuickStart ; gradlew lives in the repo root. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +cd "$REPO_ROOT" + +# --- fake Google/Firebase service-account json (valid RSA key, never leaves the machine) --- +# FirebaseConfig reads this file at startup unconditionally, so it must parse — but with the +# dummy calendar provider active it is never actually used to talk to Google. +CRED_FILE="$SCRIPT_DIR/.local-fake-credentials.json" +if [ ! -f "$CRED_FILE" ]; then + KEY=$(openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 2>/dev/null | python3 -c 'import sys;print(sys.stdin.read().replace(chr(10),"\\n"),end="")') + cat > "$CRED_FILE" < beans load but never hit the network. +export PHOTO_SAVER_PROVIDER=mattermost +export PHOTO_SAVER_STORAGE=dummy +export PHOTO_SAVER_SCHEDULER_ENABLED=false + +# --- calendar / firebase (dummy provider active, so these files are only parsed, never used) --- +export GOOGLE_CREDENTIALS_FILE="$STUB" +export FIREBASE_CREDENTIALS="$STUB" +export DEFAULT_CALENDAR=primary +export CALENDAR_APPLICATION_NAME=EffectiveOfficeLocal +export CALENDAR_DELEGATED_USER=local@example.com +export DEFAULT_APP_EMAIL=local@example.com +export CALENDARS="" +export TEST_APPLICATION_URL="" +export TEST_CALENDARS="" + +# --- these provider config classes load unconditionally and read env directly, +# so they must be non-empty even though the providers are dummy --- +export SYNOLOGY_IP=dummy +export SYNOLOGY_LOGIN=dummy +export SYNOLOGY_PASSWORD=dummy +export SYNOLOGY_ALBUM_NAME=dummy +export NOTION_TOKEN=dummy +export NOTION_TEAMMATES_DB_ID=dummy +export NOTION_SUPERNOVA_DB_ID=dummy +export CLOCKIFY_API_KEY=dummy +export CLOCKIFY_WORKSPACE_ID=dummy +export CLOCKIFY_PROJECT_ID=dummy +export PHOTO_SAVER_MATTERMOST_BASE_URL="http://localhost" +export PHOTO_SAVER_MATTERMOST_TOKEN=dummy +export PHOTO_SAVER_EMOJI_REQUEST_SAVE=star +export PHOTO_SAVER_EMOJI_SUCCESS=white_check_mark +export PHOTO_SAVER_SYNOLOGY_BASE_URL="http://localhost" +export PHOTO_SAVER_SYNOLOGY_USERNAME=dummy +export PHOTO_SAVER_SYNOLOGY_PASSWORD=dummy +export PHOTO_SAVER_ALBUM_NAME=dummy + +exec ./gradlew :backend:app:bootRun --args='--spring.profiles.active=local' --console=plain diff --git a/localQuickStart/seed-local-db.sh b/localQuickStart/seed-local-db.sh new file mode 100755 index 000000000..27cc525e6 --- /dev/null +++ b/localQuickStart/seed-local-db.sh @@ -0,0 +1,57 @@ +#!/usr/bin/env bash +# Seed the local Postgres so the tablet app has something to show: +# - an API key the clients authenticate with (Bearer token) +# - one workspace zone + two meeting rooms (Sync, Focus) +# +# The backend stores API keys as the lowercase hex SHA-256 of the raw key. +# The raw key below MUST match `apiKey` in the repo-root local.properties. +# +# Safe to re-run (idempotent). Requires the postgres-effectiveoffice container to be running. +set -euo pipefail + +CONTAINER="${PG_CONTAINER:-postgres-effectiveoffice}" +API_KEY="${LOCAL_API_KEY:-effective-office-local-key}" + +# NOTE: docker exec needs no stdin here because we pass SQL via -c. +psql() { docker exec "$CONTAINER" psql -U postgres -d effectiveoffice -v ON_ERROR_STOP=1 "$@"; } + +HASH="$(printf '%s' "$API_KEY" | shasum -a 256 | awk '{print $1}')" +echo "Seeding API key '$API_KEY' (sha256=$HASH)" + +psql -c "INSERT INTO api_keys (id, key_value, description) + VALUES (gen_random_uuid(), '$HASH', 'local dev key') + ON CONFLICT (key_value) DO NOTHING;" + +psql -c "INSERT INTO workspace_zones(id,name) VALUES (gen_random_uuid(),'Ground floor') + ON CONFLICT (name) DO NOTHING;" + +for room in Sync Focus; do + psql -c "INSERT INTO workspaces(id,name,tag,zone_id) + SELECT gen_random_uuid(),'$room','meeting', z.id + FROM workspace_zones z WHERE z.name='Ground floor' + ON CONFLICT (name) DO NOTHING;" +done + +echo "--- current meeting rooms ---" +psql -c "SELECT w.name, w.tag, z.name AS zone + FROM workspaces w LEFT JOIN workspace_zones z ON z.id=w.zone_id + WHERE w.tag='meeting';" + +# Organizers = the booking editor's "Choose organizer" list. +# NOTE: the tablet client filters users by tag == "employer" (a typo in +# OrganizerRepositoryImpl — everything else uses "employee"), and the backend +# ignores the user_tag query param, so the DB tag must be 'employer' to show up. +for row in "jdoe|john.doe@example.com|John|Doe" \ + "asmith|anna.smith@example.com|Anna|Smith" \ + "mkim|min.kim@example.com|Min|Kim"; do + IFS='|' read -r username email first last <<< "$row" + psql -c "INSERT INTO users (id, username, email, first_name, last_name, created_at, updated_at, active, role, tag) + VALUES (gen_random_uuid(), '$username', '$email', '$first', '$last', now(), now(), true, 'employer', 'employer') + ON CONFLICT (email) DO NOTHING;" +done +# Fix any pre-existing rows seeded with the wrong tag. +psql -c "UPDATE users SET tag='employer' WHERE tag='employee';" + +echo "--- current organizers (tag=employer) ---" +psql -c "SELECT first_name, last_name, email FROM users WHERE tag='employer';" +echo "Done." From 4727661c751283bf95c7a493d426da8bd531b038 Mon Sep 17 00:00:00 2001 From: denwritescode Date: Mon, 13 Jul 2026 22:54:51 +0300 Subject: [PATCH 3/8] + fixing compilation errors for tablet iOS target --- .../office/shared/core/utils/DateTimeUtils.ios.kt | 2 -- .../tablet/core/data/network/HttpClientFactory.ios.kt | 8 -------- .../office/tablet/core/ui/utils/DateFormatter.ios.kt | 3 ++- 3 files changed, 2 insertions(+), 11 deletions(-) delete mode 100644 clients/tablet/core/data/src/iosMain/kotlin/band/effective/office/tablet/core/data/network/HttpClientFactory.ios.kt diff --git a/clients/shared/core/src/iosMain/kotlin/band/effective/office/shared/core/utils/DateTimeUtils.ios.kt b/clients/shared/core/src/iosMain/kotlin/band/effective/office/shared/core/utils/DateTimeUtils.ios.kt index 74ee65211..47c03b2e4 100644 --- a/clients/shared/core/src/iosMain/kotlin/band/effective/office/shared/core/utils/DateTimeUtils.ios.kt +++ b/clients/shared/core/src/iosMain/kotlin/band/effective/office/shared/core/utils/DateTimeUtils.ios.kt @@ -4,12 +4,10 @@ import kotlinx.datetime.LocalDateTime import kotlinx.datetime.toNSDateComponents import platform.Foundation.NSCalendar import platform.Foundation.NSDateFormatter -import platform.Foundation.NSLocale actual fun LocalDateTime.toLocalisedString(pattern: String): String { val dateFormatter = NSDateFormatter() dateFormatter.dateFormat = pattern - dateFormatter.locale = NSLocale.currentLocale val calendar = NSCalendar.currentCalendar val dateComponents = toNSDateComponents() diff --git a/clients/tablet/core/data/src/iosMain/kotlin/band/effective/office/tablet/core/data/network/HttpClientFactory.ios.kt b/clients/tablet/core/data/src/iosMain/kotlin/band/effective/office/tablet/core/data/network/HttpClientFactory.ios.kt deleted file mode 100644 index 370de4437..000000000 --- a/clients/tablet/core/data/src/iosMain/kotlin/band/effective/office/tablet/core/data/network/HttpClientFactory.ios.kt +++ /dev/null @@ -1,8 +0,0 @@ -package band.effective.office.tablet.core.data.network - -import io.ktor.client.HttpClient -import io.ktor.client.engine.darwin.Darwin - -actual object HttpClientFactory { - actual fun createHttpClient(): HttpClient = HttpClient(Darwin) -} \ No newline at end of file diff --git a/clients/tablet/core/ui/src/iosMain/kotlin/band/effective/office/tablet/core/ui/utils/DateFormatter.ios.kt b/clients/tablet/core/ui/src/iosMain/kotlin/band/effective/office/tablet/core/ui/utils/DateFormatter.ios.kt index 28a122dee..74d9d1260 100644 --- a/clients/tablet/core/ui/src/iosMain/kotlin/band/effective/office/tablet/core/ui/utils/DateFormatter.ios.kt +++ b/clients/tablet/core/ui/src/iosMain/kotlin/band/effective/office/tablet/core/ui/utils/DateFormatter.ios.kt @@ -2,5 +2,6 @@ package band.effective.office.tablet.core.ui.utils import platform.Foundation.NSLocale import platform.Foundation.currentLocale +import platform.Foundation.languageCode -actual fun getCurrentLanguageCode(): String = NSLocale.currentLocale.languageCode \ No newline at end of file +actual fun getCurrentLanguageCode(): String = NSLocale.currentLocale.languageCode ?: "en" \ No newline at end of file From 89f50206dea62b99393c5f1f9d667bef9051d130 Mon Sep 17 00:00:00 2001 From: denwritescode Date: Mon, 13 Jul 2026 22:55:18 +0300 Subject: [PATCH 4/8] + upgrade to Kotlin 2.3.10 & Compose Multiplatform 1.10.2 --- build-logic/build.gradle.kts | 40 +++++++++++-------- ...ve.office.backend.kotlin-common.gradle.kts | 6 +-- build.gradle.kts | 14 +++++-- .../core/utils/LocalDateTimeExtensions.kt | 2 +- clients/smsrouter/app/build.gradle.kts | 9 +++-- clients/tablet/composeApp/build.gradle.kts | 16 ++++---- .../office/tablet/time/TimeReceiver.kt | 2 +- .../office/tablet/time/TimeReceiver.kt | 2 +- .../tablet/core/domain/model/RoomInfo.kt | 2 +- .../core/domain/useCase/RoomInfoUseCase.kt | 2 +- .../core/domain/useCase/SelectRoomUseCase.kt | 2 +- .../presentation/BookingEditorComponent.kt | 2 +- clients/tablet/feature/main/build.gradle.kts | 3 +- .../feature/main/components/RoomInfoView.kt | 2 +- .../feature/main/domain/CurrentTimeHolder.kt | 2 +- .../main/domain/GetTimeToNextEventUseCase.kt | 2 +- clients/tv/composeApp/build.gradle.kts | 2 +- .../presentation/components/ProgressButton.kt | 2 +- .../stories/domain/service/DateUtils.kt | 2 +- gradle/libs.versions.toml | 14 ++++--- gradle/wrapper/gradle-wrapper.properties | 2 +- 21 files changed, 75 insertions(+), 55 deletions(-) diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index 5738dbb48..6de4b5c3a 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -14,24 +14,30 @@ repositories { } dependencies { - implementation("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:2.1.21") - implementation("org.springframework.boot:org.springframework.boot.gradle.plugin:3.5.0") - implementation("org.jetbrains.kotlin.plugin.spring:org.jetbrains.kotlin.plugin.spring.gradle.plugin:2.1.21") - implementation("org.jetbrains.kotlin.plugin.jpa:org.jetbrains.kotlin.plugin.jpa.gradle.plugin:2.1.21") - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.21") - implementation("org.jetbrains.kotlin:kotlin-allopen:2.1.21") - implementation("org.jetbrains.kotlin:kotlin-noarg:2.1.21") - implementation("org.springframework.boot:spring-boot-gradle-plugin:3.5.0") - implementation("io.spring.gradle:dependency-management-plugin:1.1.7") + val kotlinVersion = libs.versions.kotlin.get() + val springBootVersion = libs.versions.springBoot.get() + val springDepManagementVersion = libs.versions.springDependencyManagement.get() + val agpVersion = libs.versions.agp.get() + val composeGradlePluginVersion = libs.versions.compose.get() + val googleServicesVersion = libs.versions.googleServices.get() - implementation("org.jetbrains.kotlin.multiplatform:org.jetbrains.kotlin.multiplatform.gradle.plugin:2.1.21") - implementation("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:2.1.21") - implementation("org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin:2.1.21") - implementation("com.android.tools.build:gradle:8.9.1") { + implementation("org.jetbrains.kotlin.jvm:org.jetbrains.kotlin.jvm.gradle.plugin:$kotlinVersion") + implementation("org.springframework.boot:org.springframework.boot.gradle.plugin:$springBootVersion") + implementation("org.jetbrains.kotlin.plugin.spring:org.jetbrains.kotlin.plugin.spring.gradle.plugin:$kotlinVersion") + implementation("org.jetbrains.kotlin.plugin.jpa:org.jetbrains.kotlin.plugin.jpa.gradle.plugin:$kotlinVersion") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") + implementation("org.jetbrains.kotlin:kotlin-allopen:$kotlinVersion") + implementation("org.jetbrains.kotlin:kotlin-noarg:$kotlinVersion") + implementation("org.springframework.boot:spring-boot-gradle-plugin:$springBootVersion") + implementation("io.spring.gradle:dependency-management-plugin:$springDepManagementVersion") + + implementation("org.jetbrains.kotlin.multiplatform:org.jetbrains.kotlin.multiplatform.gradle.plugin:$kotlinVersion") + implementation("org.jetbrains.kotlin.plugin.compose:org.jetbrains.kotlin.plugin.compose.gradle.plugin:$kotlinVersion") + implementation("org.jetbrains.kotlin.plugin.serialization:org.jetbrains.kotlin.plugin.serialization.gradle.plugin:$kotlinVersion") + implementation("com.android.tools.build:gradle:$agpVersion") { exclude(group = "org.apache.commons", module = "commons-compress") } - implementation("org.jetbrains.compose:org.jetbrains.compose.gradle.plugin:1.8.1") - implementation("com.google.gms:google-services:4.4.3") - implementation("org.jetbrains.kotlin.kapt:org.jetbrains.kotlin.kapt.gradle.plugin:2.1.21") - + implementation("org.jetbrains.compose:org.jetbrains.compose.gradle.plugin:$composeGradlePluginVersion") + implementation("com.google.gms:google-services:$googleServicesVersion") + implementation("org.jetbrains.kotlin.kapt:org.jetbrains.kotlin.kapt.gradle.plugin:$kotlinVersion") } \ No newline at end of file diff --git a/build-logic/src/main/kotlin/band/effective/office/backend/band.effective.office.backend.kotlin-common.gradle.kts b/build-logic/src/main/kotlin/band/effective/office/backend/band.effective.office.backend.kotlin-common.gradle.kts index 476cbc83f..e64e6fa59 100644 --- a/build-logic/src/main/kotlin/band/effective/office/backend/band.effective.office.backend.kotlin-common.gradle.kts +++ b/build-logic/src/main/kotlin/band/effective/office/backend/band.effective.office.backend.kotlin-common.gradle.kts @@ -10,9 +10,9 @@ java { } tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = JavaVersion.VERSION_17.toString() + compilerOptions { + freeCompilerArgs.add("-Xjsr305=strict") + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) } } diff --git a/build.gradle.kts b/build.gradle.kts index 907a94ae4..4415b01f6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -37,9 +37,17 @@ subprojects { } tasks.withType { - kotlinOptions { - freeCompilerArgs = listOf("-Xjsr305=strict") - jvmTarget = JavaVersion.VERSION_17.toString() + compilerOptions { + freeCompilerArgs.add("-Xjsr305=strict") + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + } + } + + // kotlinx-datetime 0.7.x is built on the still-experimental kotlin.time.Clock/Instant, + // so opt in project-wide for every Kotlin compilation (JVM, Android and Native). + tasks.withType>().configureEach { + compilerOptions { + optIn.add("kotlin.time.ExperimentalTime") } } diff --git a/clients/shared/core/src/commonMain/kotlin/band/effective/office/shared/core/utils/LocalDateTimeExtensions.kt b/clients/shared/core/src/commonMain/kotlin/band/effective/office/shared/core/utils/LocalDateTimeExtensions.kt index bb8c95b63..eac2baab2 100644 --- a/clients/shared/core/src/commonMain/kotlin/band/effective/office/shared/core/utils/LocalDateTimeExtensions.kt +++ b/clients/shared/core/src/commonMain/kotlin/band/effective/office/shared/core/utils/LocalDateTimeExtensions.kt @@ -2,7 +2,7 @@ package band.effective.office.shared.core.utils import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime diff --git a/clients/smsrouter/app/build.gradle.kts b/clients/smsrouter/app/build.gradle.kts index bcb0ea09f..efb4c22ba 100644 --- a/clients/smsrouter/app/build.gradle.kts +++ b/clients/smsrouter/app/build.gradle.kts @@ -41,15 +41,18 @@ android { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = "17" - } buildFeatures { compose = true buildConfig = true } } +kotlin { + compilerOptions { + jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17) + } +} + dependencies { implementation(libs.androidx.core.ktx) implementation(libs.androidx.lifecycle.runtime.ktx) diff --git a/clients/tablet/composeApp/build.gradle.kts b/clients/tablet/composeApp/build.gradle.kts index 1b90aa857..d4ee7dae4 100644 --- a/clients/tablet/composeApp/build.gradle.kts +++ b/clients/tablet/composeApp/build.gradle.kts @@ -1,13 +1,13 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties plugins { - id("org.jetbrains.kotlin.multiplatform") - id("org.jetbrains.kotlin.plugin.compose") version "2.1.21" - id("org.jetbrains.compose") - id("com.android.application") - id("org.jetbrains.kotlin.plugin.serialization") version "2.1.21" + alias(libs.plugins.multiplatform) + alias(libs.plugins.kotlin.compose) + alias(libs.plugins.compose) + alias(libs.plugins.android.application) + alias(libs.plugins.kotlinx.serialization) alias(libs.plugins.buildkonfig) - id("com.google.gms.google-services") version "4.4.3" + alias(libs.plugins.google.services) id("sign-config") } @@ -30,7 +30,9 @@ kotlin { export("com.mohamedrejeb.calf:calf-ui:0.8.0") } it.compilations.all { - kotlinOptions.freeCompilerArgs += "-Xdisable-decompose-parcelize" + compileTaskProvider.configure { + compilerOptions.freeCompilerArgs.add("-Xdisable-decompose-parcelize") + } } } diff --git a/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/time/TimeReceiver.kt b/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/time/TimeReceiver.kt index 60c5e84f7..8e4dc5c21 100644 --- a/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/time/TimeReceiver.kt +++ b/clients/tablet/composeApp/src/androidMain/kotlin/band/effective/office/tablet/time/TimeReceiver.kt @@ -7,7 +7,7 @@ import android.content.IntentFilter import android.util.Log import band.effective.office.tablet.feature.main.domain.CurrentTimeHolder import kotlinx.coroutines.flow.StateFlow -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime diff --git a/clients/tablet/composeApp/src/iosMain/kotlin/band/effective/office/tablet/time/TimeReceiver.kt b/clients/tablet/composeApp/src/iosMain/kotlin/band/effective/office/tablet/time/TimeReceiver.kt index 17e7c1556..9b4d8dbee 100644 --- a/clients/tablet/composeApp/src/iosMain/kotlin/band/effective/office/tablet/time/TimeReceiver.kt +++ b/clients/tablet/composeApp/src/iosMain/kotlin/band/effective/office/tablet/time/TimeReceiver.kt @@ -2,7 +2,7 @@ package band.effective.office.tablet.time import band.effective.office.tablet.feature.main.domain.CurrentTimeHolder import kotlinx.coroutines.flow.StateFlow -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime diff --git a/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/RoomInfo.kt b/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/RoomInfo.kt index 8f224438e..deb5f5312 100644 --- a/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/RoomInfo.kt +++ b/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/model/RoomInfo.kt @@ -1,6 +1,6 @@ package band.effective.office.tablet.core.domain.model -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.DatePeriod import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime diff --git a/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/RoomInfoUseCase.kt b/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/RoomInfoUseCase.kt index 071e1d3cf..632ceff52 100644 --- a/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/RoomInfoUseCase.kt +++ b/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/RoomInfoUseCase.kt @@ -4,7 +4,7 @@ import band.effective.office.shared.core.domain.map import band.effective.office.tablet.core.domain.model.RoomInfo import band.effective.office.shared.core.domain.unbox import kotlin.collections.filter -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.coroutines.flow.map import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant diff --git a/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/SelectRoomUseCase.kt b/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/SelectRoomUseCase.kt index 3d16a7085..9000c237a 100644 --- a/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/SelectRoomUseCase.kt +++ b/clients/tablet/core/domain/src/commonMain/kotlin/band/effective/office/tablet/core/domain/useCase/SelectRoomUseCase.kt @@ -6,7 +6,7 @@ import band.effective.office.shared.core.utils.currentInstant import kotlin.math.absoluteValue import kotlin.time.Duration import kotlin.time.Duration.Companion.minutes -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.Instant import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant diff --git a/clients/tablet/feature/bookingEditor/src/commonMain/kotlin/band/effective/office/tablet/feature/bookingEditor/presentation/BookingEditorComponent.kt b/clients/tablet/feature/bookingEditor/src/commonMain/kotlin/band/effective/office/tablet/feature/bookingEditor/presentation/BookingEditorComponent.kt index 355e9fb96..f36b3321d 100644 --- a/clients/tablet/feature/bookingEditor/src/commonMain/kotlin/band/effective/office/tablet/feature/bookingEditor/presentation/BookingEditorComponent.kt +++ b/clients/tablet/feature/bookingEditor/src/commonMain/kotlin/band/effective/office/tablet/feature/bookingEditor/presentation/BookingEditorComponent.kt @@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime diff --git a/clients/tablet/feature/main/build.gradle.kts b/clients/tablet/feature/main/build.gradle.kts index 842627990..2f317920b 100644 --- a/clients/tablet/feature/main/build.gradle.kts +++ b/clients/tablet/feature/main/build.gradle.kts @@ -6,8 +6,7 @@ kotlin { sourceSets { commonMain.dependencies { implementation(project(":clients:tablet:feature:slot")) - implementation("org.jetbrains.compose.components:components-ui-tooling-preview:1.10.0") - + implementation(compose.components.uiToolingPreview) } } } diff --git a/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/components/RoomInfoView.kt b/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/components/RoomInfoView.kt index 719ddf98b..75c92db90 100644 --- a/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/components/RoomInfoView.kt +++ b/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/components/RoomInfoView.kt @@ -12,7 +12,7 @@ import band.effective.office.tablet.core.domain.model.nextEvent import band.effective.office.tablet.core.ui.theme.AppTheme import band.effective.office.tablet.feature.main.components.uiComponent.BusyRoomInfoComponent import band.effective.office.tablet.feature.main.components.uiComponent.FreeRoomInfoComponent -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.DatePeriod import kotlinx.datetime.LocalDateTime import kotlinx.datetime.LocalTime diff --git a/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/CurrentTimeHolder.kt b/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/CurrentTimeHolder.kt index 50204e610..ec2e56722 100644 --- a/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/CurrentTimeHolder.kt +++ b/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/CurrentTimeHolder.kt @@ -3,7 +3,7 @@ package band.effective.office.tablet.feature.main.domain import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.LocalDateTime import kotlinx.datetime.TimeZone import kotlinx.datetime.toLocalDateTime diff --git a/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/GetTimeToNextEventUseCase.kt b/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/GetTimeToNextEventUseCase.kt index e78abe2ac..29742246c 100644 --- a/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/GetTimeToNextEventUseCase.kt +++ b/clients/tablet/feature/main/src/commonMain/kotlin/band/effective/office/tablet/feature/main/domain/GetTimeToNextEventUseCase.kt @@ -2,7 +2,7 @@ package band.effective.office.tablet.feature.main.domain import band.effective.office.tablet.core.domain.model.RoomInfo import band.effective.office.tablet.core.domain.model.nextEvent -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.TimeZone import kotlinx.datetime.toInstant diff --git a/clients/tv/composeApp/build.gradle.kts b/clients/tv/composeApp/build.gradle.kts index 96d098ef4..bcb9d4902 100644 --- a/clients/tv/composeApp/build.gradle.kts +++ b/clients/tv/composeApp/build.gradle.kts @@ -2,7 +2,7 @@ import com.android.build.gradle.internal.cxx.configure.gradleLocalProperties plugins { alias(libs.plugins.multiplatform) - id("org.jetbrains.kotlin.plugin.compose") version "2.1.21" + alias(libs.plugins.kotlin.compose) alias(libs.plugins.compose) alias(libs.plugins.android.application) alias(libs.plugins.kotlinx.serialization) diff --git a/clients/tv/feature/menu/src/commonMain/kotlin/band/effective/office/tv/feature/menu/presentation/components/ProgressButton.kt b/clients/tv/feature/menu/src/commonMain/kotlin/band/effective/office/tv/feature/menu/presentation/components/ProgressButton.kt index 32c2f6051..e6c281d01 100644 --- a/clients/tv/feature/menu/src/commonMain/kotlin/band/effective/office/tv/feature/menu/presentation/components/ProgressButton.kt +++ b/clients/tv/feature/menu/src/commonMain/kotlin/band/effective/office/tv/feature/menu/presentation/components/ProgressButton.kt @@ -28,7 +28,7 @@ import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.input.key.type import band.effective.office.tv.core.ui.theme.LocalTvColorsPalette import band.effective.office.tv.core.ui.theme.LocalTvShapes -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.coroutines.delay private const val AUTO_CLICK_DELAY_MS = 15000L // 15 seconds diff --git a/clients/tv/feature/stories/src/commonMain/kotlin/band/effective/office/tv/feature/stories/domain/service/DateUtils.kt b/clients/tv/feature/stories/src/commonMain/kotlin/band/effective/office/tv/feature/stories/domain/service/DateUtils.kt index 5b37f50e2..a1009ae56 100644 --- a/clients/tv/feature/stories/src/commonMain/kotlin/band/effective/office/tv/feature/stories/domain/service/DateUtils.kt +++ b/clients/tv/feature/stories/src/commonMain/kotlin/band/effective/office/tv/feature/stories/domain/service/DateUtils.kt @@ -1,6 +1,6 @@ package band.effective.office.tv.feature.stories.domain.service -import kotlinx.datetime.Clock +import kotlin.time.Clock import kotlinx.datetime.DateTimeUnit import kotlinx.datetime.LocalDate import kotlinx.datetime.LocalDateTime diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 997ee5858..0dc43a829 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] # Common ## Kotlin -kotlin = "2.1.21" +kotlin = "2.3.10" kotlinCoroutines = "1.10.2" kotlinx-serialization = "1.8.1" @@ -41,10 +41,10 @@ googleOAuthClient = "1.33.3" android-compileSdk = "35" android-minSdk = "26" android-targetSdk = "35" -agp = "8.9.3" +agp = "8.11.2" ## Compose -compose = "1.8.1" +compose = "1.10.2" hotReload = "1.0.0-alpha09" ## Androidx @@ -71,10 +71,10 @@ koin = "4.1.0" ## Storage multiplatformSettings = "1.3.0" buildkonfig = "0.15.1" -room = "2.7.2" +room = "2.8.4" ## Datetime -datetime = "0.6.2" +datetime = "0.7.1" ## Decompose decompose = "3.3.0" @@ -84,6 +84,7 @@ essenty-ios = "0.2.4" ## Settings settings = "1.3.0" firebaseMessagingKtx = "24.1.2" +googleServices = "4.4.3" ## Notion notion-ver = "1.11.1" @@ -239,11 +240,12 @@ spring-dependency-management = { id = "io.spring.dependency-management", version # Client multiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } kotlin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" } -compose = { id = "org.jetbrains.compose", version = "1.8.1" } +compose = { id = "org.jetbrains.compose", version.ref = "compose" } android-application = { id = "com.android.application", version.ref = "agp" } kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } hotReload = { id = "org.jetbrains.compose.hot-reload", version.ref = "hotReload" } buildkonfig = { id = "com.codingfeline.buildkonfig", version.ref = "buildkonfig" } +google-services = { id = "com.google.gms.google-services", version.ref = "googleServices" } [bundles] # Common diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 780979f3a..7243b7272 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Wed Jun 25 13:54:29 OMST 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists From 98a1d580d0deccd84e13264b36a800f4217219bd Mon Sep 17 00:00:00 2001 From: denwritescode Date: Tue, 14 Jul 2026 22:47:14 +0300 Subject: [PATCH 5/8] + update calf-ui 0.8.0 -> 0.11.0 (fixes date/time picker crash) --- .../backend/band.effective.office.client.kmp.ui.gradle.kts | 2 +- clients/tablet/composeApp/build.gradle.kts | 4 ++-- gradle/libs.versions.toml | 4 +++- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/build-logic/src/main/kotlin/band/effective/office/backend/band.effective.office.client.kmp.ui.gradle.kts b/build-logic/src/main/kotlin/band/effective/office/backend/band.effective.office.client.kmp.ui.gradle.kts index beeda233f..638ab6541 100644 --- a/build-logic/src/main/kotlin/band/effective/office/backend/band.effective.office.client.kmp.ui.gradle.kts +++ b/build-logic/src/main/kotlin/band/effective/office/backend/band.effective.office.client.kmp.ui.gradle.kts @@ -17,7 +17,7 @@ kotlin { implementation(compose.components.uiToolingPreview) implementation(libs.findLibrary("coil").get()) implementation(libs.findLibrary("coil.network.ktor").get()) - api("com.mohamedrejeb.calf:calf-ui:0.8.0") + api(libs.findLibrary("calf-ui").get()) } androidMain.dependencies { diff --git a/clients/tablet/composeApp/build.gradle.kts b/clients/tablet/composeApp/build.gradle.kts index d4ee7dae4..6b41e8279 100644 --- a/clients/tablet/composeApp/build.gradle.kts +++ b/clients/tablet/composeApp/build.gradle.kts @@ -27,7 +27,7 @@ kotlin { export(libs.decompose.compose.jetbrains) export(libs.essenty.lifecycle) - export("com.mohamedrejeb.calf:calf-ui:0.8.0") + export(libs.calf.ui) } it.compilations.all { compileTaskProvider.configure { @@ -47,7 +47,7 @@ kotlin { api(libs.decompose) api(libs.decompose.compose.jetbrains) - api("com.mohamedrejeb.calf:calf-ui:0.8.0") + api(libs.calf.ui) api(libs.kotlinx.datetime) implementation(libs.kotlin.coroutines.core) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 0dc43a829..476bd43c4 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -38,7 +38,7 @@ googleOAuthClient = "1.33.3" # Client ## Android -android-compileSdk = "35" +android-compileSdk = "36" android-minSdk = "26" android-targetSdk = "35" agp = "8.11.2" @@ -64,6 +64,7 @@ ktor = "3.1.3" ## UI coil = "3.2.0" zxing = "3.5.1" +calf = "0.11.0" ## DI koin = "4.1.0" @@ -197,6 +198,7 @@ ktor-client-winhttp = { module = "io.ktor:ktor-client-winhttp", version.ref = "k ## UI coil = { module = "io.coil-kt.coil3:coil-compose-core", version.ref = "coil" } coil-network-ktor = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } +calf-ui = { module = "com.mohamedrejeb.calf:calf-ui", version.ref = "calf" } zxing-core = { module = "com.google.zxing:core", version.ref = "zxing" } ## DI From f5c27562c606ead020a2830e54c8a92d3ca0a212 Mon Sep 17 00:00:00 2001 From: denwritescode Date: Fri, 7 Aug 2026 12:39:48 +0300 Subject: [PATCH 6/8] seed enough organizers to have something to scroll MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three names fill the list past its 150.dp cap by about nine, so the only thing the scroll could do was travel that nine and settle back — which is indistinguishable from a scrolling bug, and was reported as one. Twenty names, of varying length and script, so wrapping and clipping are visible here too. Co-Authored-By: Claude Opus 5 (cherry picked from commit 55b4e429eb45a76ec95403a7fca62b6fb9b95e6e) --- localQuickStart/seed-local-db.sh | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/localQuickStart/seed-local-db.sh b/localQuickStart/seed-local-db.sh index 27cc525e6..512d4f1bb 100755 --- a/localQuickStart/seed-local-db.sh +++ b/localQuickStart/seed-local-db.sh @@ -41,9 +41,29 @@ psql -c "SELECT w.name, w.tag, z.name AS zone # NOTE: the tablet client filters users by tag == "employer" (a typo in # OrganizerRepositoryImpl — everything else uses "employee"), and the backend # ignores the user_tag query param, so the DB tag must be 'employer' to show up. +# Enough of them to fill the list past its 150.dp cap, or scrolling cannot be looked at: with +# three names there is nothing to scroll and no way to tell a scrolling bug from a still list. +# Names vary in length on purpose, so text that wraps or gets clipped shows up here too. for row in "jdoe|john.doe@example.com|John|Doe" \ "asmith|anna.smith@example.com|Anna|Smith" \ - "mkim|min.kim@example.com|Min|Kim"; do + "mkim|min.kim@example.com|Min|Kim" \ + "pivanov|pyotr.ivanov@example.com|Пётр|Иванов" \ + "esokolova|elena.sokolova@example.com|Елена|Соколова" \ + "dvolkov|dmitry.volkov@example.com|Дмитрий|Волков" \ + "akonstantinopolskaya|a.konst@example.com|Анастасия|Константинопольская" \ + "ilee|isabella.lee@example.com|Isabella|Lee" \ + "mokonkwo|m.okonkwo@example.com|Chukwuemeka|Okonkwo" \ + "ttanaka|t.tanaka@example.com|Takeshi|Tanaka" \ + "sgarcia|s.garcia@example.com|Sofia|Garcia" \ + "obrown|o.brown@example.com|Oliver|Brown" \ + "nnovak|n.novak@example.com|Nina|Novak" \ + "rmuller|r.muller@example.com|Rudolf|Müller" \ + "ksmirnova|k.smirnova@example.com|Ксения|Смирнова" \ + "vpetrov|v.petrov@example.com|Владимир|Петров" \ + "ahassan|a.hassan@example.com|Amina|Hassan" \ + "lrossi|l.rossi@example.com|Luca|Rossi" \ + "ykim|y.kim@example.com|Yuna|Kim" \ + "bandersson|b.andersson@example.com|Bjorn|Andersson"; do IFS='|' read -r username email first last <<< "$row" psql -c "INSERT INTO users (id, username, email, first_name, last_name, created_at, updated_at, active, role, tag) VALUES (gen_random_uuid(), '$username', '$email', '$first', '$last', now(), now(), true, 'employer', 'employer') From 8a227ca19e569fe3a37450530ad9f80a1740c271 Mon Sep 17 00:00:00 2001 From: denwritescode Date: Thu, 13 Aug 2026 10:05:38 +0300 Subject: [PATCH 7/8] add a script that points the client at the local backend per platform The base URL is compiled in and each platform reaches the host differently, so switching platforms meant remembering which of two lines to edit and which address goes where. Aurora is in the list because its build is release and reads the other property entirely. Co-Authored-By: Claude Opus 5 --- localQuickStart/README.md | 15 ++++++---- localQuickStart/point-client-at-local.sh | 36 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 5 deletions(-) create mode 100755 localQuickStart/point-client-at-local.sh diff --git a/localQuickStart/README.md b/localQuickStart/README.md index 35a333d6e..100b5fa45 100644 --- a/localQuickStart/README.md +++ b/localQuickStart/README.md @@ -74,12 +74,17 @@ The client reads its config from the **repo-root `local.properties`** ⚠️ **The base URL is baked into the build**, and it differs per platform: -| Target | `api.url.debug` | -|--------|-----------------| -| **iOS simulator** (shares the host network) | `http://localhost:8080` | -| **Android emulator** (host is reachable via a special IP) | `http://10.0.2.2:8080` | +| Target | Property | Value | +|--------|----------|-------| +| **iOS simulator** (shares the host network) | `api.url.debug` | `http://localhost:8080` | +| **Android emulator** (host is reachable via a special IP) | `api.url.debug` | `http://10.0.2.2:8080` | +| **Aurora emulator** (same IP, but the build is release) | `api.url.release` | `http://10.0.2.2:8080` | -Change that one line and rebuild when you switch platforms. +Switching platforms means editing that line and rebuilding. The script does the editing: + +```bash +localQuickStart/point-client-at-local.sh ios|android|aurora +``` ### Android emulator diff --git a/localQuickStart/point-client-at-local.sh b/localQuickStart/point-client-at-local.sh new file mode 100755 index 000000000..b939574de --- /dev/null +++ b/localQuickStart/point-client-at-local.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# Point the tablet client at the local backend for one platform. +# +# The base URL is baked into the build and every platform reaches the host differently, +# so switching platforms means editing local.properties and rebuilding. Which line and +# which address is easy to get wrong, hence this script. +# +# iOS simulator shares the host network -> api.url.debug=http://localhost:8080 +# Android emulator host lives behind a special IP -> api.url.debug=http://10.0.2.2:8080 +# Aurora emulator same IP as Android, but the build is release, so it reads api.url.release +# +# Usage (from anywhere): +# localQuickStart/point-client-at-local.sh ios|android|aurora +# +# Rebuild after switching — the URL is compiled in. +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROPS="$(cd "$SCRIPT_DIR/.." && pwd)/local.properties" + +case "${1:-}" in + ios) KEY=api.url.debug URL=http://localhost:8080 ;; + android) KEY=api.url.debug URL=http://10.0.2.2:8080 ;; + aurora) KEY=api.url.release URL=http://10.0.2.2:8080 ;; + *) echo "usage: $(basename "$0") ios|android|aurora" >&2; exit 2 ;; +esac + +[ -f "$PROPS" ] || { echo "no $PROPS — see localQuickStart/README.md" >&2; exit 1; } +grep -q "^$KEY=" "$PROPS" || { echo "no $KEY in $PROPS" >&2; exit 1; } + +# BSD sed and GNU sed disagree about -i, so write through a temporary file instead. +TMP="$(mktemp)" +sed "s|^$KEY=.*|$KEY=$URL|" "$PROPS" > "$TMP" +mv "$TMP" "$PROPS" + +grep -E '^api\.url\.(debug|release)=' "$PROPS" From 9a5b4047a9bb33c0b27160ecaa7b868a8e50f91b Mon Sep 17 00:00:00 2001 From: denwritescode Date: Thu, 13 Aug 2026 10:06:01 +0300 Subject: [PATCH 8/8] warn that typing into the ios simulator goes through the host layout Text injection is ASCII and gets mapped by whatever macOS layout is active, so under a Russian layout the organizer filter looks broken while it is only receiving different letters. Co-Authored-By: Claude Opus 5 --- localQuickStart/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/localQuickStart/README.md b/localQuickStart/README.md index 100b5fa45..a8ef6907c 100644 --- a/localQuickStart/README.md +++ b/localQuickStart/README.md @@ -107,6 +107,10 @@ xcodebuild -project iosApp/iosApp.xcodeproj -scheme iosApp -sdk iphonesimulator To iterate on Kotlin/Native compile errors fast (without Xcode): `./gradlew :clients:tablet:composeApp:linkDebugFrameworkIosSimulatorArm64` +⚠️ **Typing into the simulator is ASCII-only and goes through the active macOS layout.** +With a Russian layout `an` arrives as `фт`, and filtering the organizer list then looks broken +when it is not. Switch to an English layout before typing. + --- ## Files this quick-start needs elsewhere in the repo