From 873b56afcce33da11f58593fc7481e7e0ac5ea50 Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Sun, 24 May 2026 20:51:54 +0200 Subject: [PATCH 1/3] Add per-widget list selector with chevron dropdown - Add chevron icon to widget header; tapping it opens a dialog picker showing all available lists - Selection is persisted per widget instance in SharedPreferences (key: "selected_list_" in "widget_prefs") - Falls back to the latest list when no selection is saved or the saved list has been deleted; clears stale prefs automatically - Cleans up prefs when a widget instance is removed (onDeleted) Co-Authored-By: Claude Sonnet 4.6 --- NATIVE_CHANGES.md | 36 ++++++---- android/app/build.gradle | 4 +- android/app/src/main/AndroidManifest.xml | 5 ++ .../widget/NoteListRemoteViewsFactory.kt | 5 +- .../simplenotepad/widget/NoteListWidget.kt | 40 +++++++++--- .../widget/NoteListWidgetService.kt | 5 +- .../simplenotepad/widget/WidgetDbHelper.kt | 65 +++++++++++++++++++ .../widget/WidgetListSelectActivity.kt | 47 ++++++++++++++ .../pgarr/simplenotepad/widget/WidgetPrefs.kt | 24 +++++++ .../main/res/drawable/widget_chevron_down.xml | 15 +++++ .../src/main/res/layout/widget_note_list.xml | 37 ++++++++--- android/app/src/main/res/values/strings.xml | 1 + android/app/src/main/res/values/styles.xml | 4 ++ app.json | 6 +- package-lock.json | 4 +- package.json | 2 +- 16 files changed, 255 insertions(+), 45 deletions(-) create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetListSelectActivity.kt create mode 100644 android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetPrefs.kt create mode 100644 android/app/src/main/res/drawable/widget_chevron_down.xml diff --git a/NATIVE_CHANGES.md b/NATIVE_CHANGES.md index b31cd21..df5ab1c 100644 --- a/NATIVE_CHANGES.md +++ b/NATIVE_CHANGES.md @@ -19,21 +19,24 @@ Update this file whenever you add or modify native files. Location: `android/app/src/main/java/com/pgarr/simplenotepad/widget/` -| File | Description | -| ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -| `NoteListWidget.kt` | `AppWidgetProvider` — entry point, called by Android on widget add/update. Reads the latest list from SQLite and binds it to the `ListView` via `RemoteViewsService`. | -| `NoteListWidgetService.kt` | `RemoteViewsService` — Android requires a bound service to supply list row views to a widget `ListView`. Instantiates `NoteListRemoteViewsFactory`. | -| `NoteListRemoteViewsFactory.kt` | `RemoteViewsFactory` — builds each list row `RemoteViews`. Sets the checkbox icon (on/off), text, opacity for completed items, and attaches a fill-in `Intent` to each row for tap handling. | -| `WidgetDbHelper.kt` | Opens the app's SQLite database directly using Android's `SQLiteDatabase` API (not expo-sqlite). Provides `getLatestList()` and `toggleItem()`. Parses and writes the `note` column JSON in a format exactly matching the JS `parseListItems` / `stringifyListItems` functions in `db.ts`. | -| `WidgetUpdateReceiver.kt` | `BroadcastReceiver` — receives checkbox tap broadcasts, calls `WidgetDbHelper.toggleItem()`, then calls `notifyAppWidgetViewDataChanged` to re-render the widget list. | +| File | Description | +| ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `NoteListWidget.kt` | `AppWidgetProvider` — entry point, called by Android on widget add/update. Resolves which list to show (via `WidgetDbHelper.resolveList`), binds it to the `ListView`, and sets up the title click (opens the list in the app) and chevron click (opens `WidgetListSelectActivity`). Cleans up prefs on widget delete. | +| `NoteListWidgetService.kt` | `RemoteViewsService` — Android requires a bound service to supply list row views to a widget `ListView`. Instantiates `NoteListRemoteViewsFactory`. | +| `NoteListRemoteViewsFactory.kt` | `RemoteViewsFactory` — builds each list row `RemoteViews`. Sets the checkbox icon (on/off), text, opacity for completed items, and attaches a fill-in `Intent` to each row for tap handling. | +| `WidgetDbHelper.kt` | Opens the app's SQLite database directly using Android's `SQLiteDatabase` API (not expo-sqlite). Provides `getLatestList()`, `getAllLists()`, `getListById()`, `resolveList()`, and `toggleItem()`. Parses and writes the `note` column JSON matching the JS `parseListItems` / `stringifyListItems` functions. | +| `WidgetPrefs.kt` | SharedPreferences helper. Persists the selected list ID per widget instance using the key `"selected_list_"` in the `"widget_prefs"` file. A value of `-1` means "no explicit selection; use latest". | +| `WidgetListSelectActivity.kt` | Dialog-themed `AppCompatActivity` launched by tapping the chevron icon in the widget header. Queries all lists via `WidgetDbHelper.getAllLists()`, shows an `AlertDialog` list picker, saves the selection to `WidgetPrefs`, then refreshes the widget. | +| `WidgetUpdateReceiver.kt` | `BroadcastReceiver` — receives checkbox tap broadcasts, calls `WidgetDbHelper.toggleItem()`, then calls `notifyAppWidgetViewDataChanged` to re-render the widget list. | #### Resource files Location: `android/app/src/main/res/` -| File | Description | -| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `layout/widget_note_list.xml` | Root widget layout. Contains a `TextView` for the list title and a `ListView` for the items. | +| File | Description | +| ------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `layout/widget_note_list.xml` | Root widget layout. Contains a horizontal header row (list title `TextView` + chevron `ImageView` for the list picker) and a `ListView` for the items. | +| `drawable/widget_chevron_down.xml` | Lucide-style chevron-down vector icon (16×16dp) used in the widget header to indicate the list selector. | | `layout/widget_list_item.xml` | Single row layout. Contains an `ImageView` acting as a checkbox (swapped between `checkbox_on_background` / `checkbox_off_background` drawables) and a `TextView` for item text. Note: real `CheckBox` views cannot be used interactively in `RemoteViews`. | | `xml/note_list_widget_info.xml` | `AppWidgetProviderInfo` — declares widget minimum size (250×180dp), resize behaviour, update interval, and initial layout. | @@ -41,7 +44,7 @@ Location: `android/app/src/main/res/` File: `android/app/src/main/AndroidManifest.xml` -Three entries added inside ``: +Four entries added inside ``: ```xml @@ -70,6 +73,13 @@ Three entries added inside ``: android:name=".widget.NoteListWidgetService" android:permission="android.permission.BIND_REMOTEVIEWS" android:exported="false" /> + + + ``` --- @@ -83,7 +93,7 @@ The widget reads from the same database and table as the main app. | Database file | `notes.db` — **must match** the string passed to `openDatabaseAsync()` in JS. Defined in `WidgetDbHelper.kt` as `DB_NAME`. | | Table | `content` | | Relevant columns | `id`, `title`, `note` (JSON string), `type` (0 = note, 1 = list) | -| Widget reads | `SELECT * FROM content WHERE type = 1 ORDER BY id DESC LIMIT 1` | +| Widget reads | `SELECT * FROM content WHERE type = 1 ORDER BY id DESC LIMIT 1` (latest/fallback); `SELECT ... WHERE id = ?` (selected) | | Widget writes | `UPDATE content SET note = ? WHERE id = ? AND type = 1` | The JSON format of the `note` column for lists is an array of `{ "checked": boolean, "text": string }` objects, matching `parseListItems` / `stringifyListItems` in `db.ts`. @@ -94,7 +104,7 @@ The JSON format of the `note` column for lists is an array of `{ "checked": bool - **Stale app state:** If the user taps a checkbox in the widget while the app is open on the same list, the app's in-memory state will be stale until it re-fetches. Add a re-fetch in your list screen's `useEffect` on `AppState` change event to handle this. - **Concurrent writes:** The widget and the app both write to the same SQLite file. SQLite WAL mode (enabled in migrations) handles concurrent reads safely, but avoid triggering widget updates and app writes simultaneously. In practice this is unlikely for a notepad app. -- **Which list is shown:** Currently the widget always shows the list with the highest `id`. There is no per-widget configuration (pinning a specific list). This could be added via `AppWidgetConfigureActivity` in a future iteration. +- **Per-widget list selection:** The chevron icon in the widget header opens a list picker. The selection is stored in `SharedPreferences` (`"widget_prefs"` file, key `"selected_list_"`). If the selected list is later deleted from the app, the widget automatically falls back to the most recently created list and clears the stale preference. - **Update interval:** Set to 30 minutes (`updatePeriodMillis="1800000"`) in `note_list_widget_info.xml`. Android batches and throttles this — do not rely on it for real-time updates. The widget updates immediately on checkbox tap via the broadcast receiver. --- diff --git a/android/app/build.gradle b/android/app/build.gradle index 5ed91aa..fb76bda 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -92,8 +92,8 @@ android { applicationId 'com.pgarr.simplenotepad' minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion - versionCode 21 - versionName "1.4.4" + versionCode 22 + versionName "1.5.0" buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\"" } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ad833b2..4f3ae1a 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -42,5 +42,10 @@ + \ No newline at end of file diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt index 2525569..a474989 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListRemoteViewsFactory.kt @@ -11,18 +11,17 @@ import com.pgarr.simplenotepad.R class NoteListRemoteViewsFactory( private val context: Context, - @Suppress("UNUSED_PARAMETER") private val listId: Int + private val widgetId: Int ) : RemoteViewsService.RemoteViewsFactory { private var items: List = emptyList() private var listTitle: String = "" - /** Id of the list rows are from — always aligned with [onDataSetChanged] (latest list). */ private var boundListId: Int = -1 override fun onCreate() {} override fun onDataSetChanged() { - val list = WidgetDbHelper.getLatestList(context) + val list = WidgetDbHelper.resolveList(context, widgetId) if (list == null) { items = emptyList() listTitle = "" diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt index c781f71..9281e0d 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidget.kt @@ -24,6 +24,13 @@ class NoteListWidget : AppWidgetProvider() { } } + override fun onDeleted(context: Context, appWidgetIds: IntArray) { + super.onDeleted(context, appWidgetIds) + for (widgetId in appWidgetIds) { + WidgetPrefs.clearSelectedListId(context, widgetId) + } + } + companion object { fun refreshAllWidgets(context: Context) { val appWidgetManager = AppWidgetManager.getInstance(context) @@ -42,15 +49,15 @@ class NoteListWidget : AppWidgetProvider() { appWidgetManager: AppWidgetManager, widgetId: Int ) { - val latestList = WidgetDbHelper.getLatestList(context) - + val displayList = WidgetDbHelper.resolveList(context, widgetId) + val rv = RemoteViews(context.packageName, R.layout.widget_note_list) - - rv.setTextViewText(R.id.widget_title, latestList?.title ?: "No lists") + + rv.setTextViewText(R.id.widget_title, displayList?.title ?: "No lists") val listDeepLink = - if (latestList != null) { - Uri.parse("simple-notepad:///list/${latestList.id}") + if (displayList != null) { + Uri.parse("simple-notepad:///list/${displayList.id}") } else { Uri.parse("simple-notepad:///") } @@ -59,7 +66,7 @@ class NoteListWidget : AppWidgetProvider() { setClass(context, MainActivity::class.java) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP) } - val openListFlags = + val pendingIntentFlags = PendingIntent.FLAG_UPDATE_CURRENT or if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { PendingIntent.FLAG_IMMUTABLE @@ -71,12 +78,25 @@ class NoteListWidget : AppWidgetProvider() { context, widgetId + 10_000, openListIntent, - openListFlags + pendingIntentFlags ) rv.setOnClickPendingIntent(R.id.widget_title, openListPendingIntent) - + + val selectListIntent = Intent(context, WidgetListSelectActivity::class.java).apply { + putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId) + data = Uri.parse("widget://select/$widgetId") + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + val selectListPendingIntent = PendingIntent.getActivity( + context, + widgetId + 20_000, + selectListIntent, + pendingIntentFlags + ) + rv.setOnClickPendingIntent(R.id.widget_dropdown_btn, selectListPendingIntent) + val serviceIntent = Intent(context, NoteListWidgetService::class.java).apply { - putExtra("list_id", latestList?.id ?: -1) + putExtra("widget_id", widgetId) data = android.net.Uri.parse("widget://list/$widgetId") } rv.setRemoteAdapter(R.id.widget_list_view, serviceIntent) diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidgetService.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidgetService.kt index 2e08ad9..0300a11 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidgetService.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/NoteListWidgetService.kt @@ -1,11 +1,12 @@ package com.pgarr.simplenotepad.widget +import android.appwidget.AppWidgetManager import android.content.Intent import android.widget.RemoteViewsService class NoteListWidgetService : RemoteViewsService() { override fun onGetViewFactory(intent: Intent): RemoteViewsFactory { - val listId = intent.getIntExtra("list_id", -1) - return NoteListRemoteViewsFactory(applicationContext, listId) + val widgetId = intent.getIntExtra("widget_id", AppWidgetManager.INVALID_APPWIDGET_ID) + return NoteListRemoteViewsFactory(applicationContext, widgetId) } } \ No newline at end of file diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt index e416628..402d1eb 100644 --- a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetDbHelper.kt @@ -57,6 +57,71 @@ object WidgetDbHelper { } } + /** Returns all lists ordered by id descending (newest first), or empty list on error. */ + fun getAllLists(context: Context): List { + val path = getDbPath(context) + return try { + val db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY) + db.use { + val cursor = it.rawQuery( + "SELECT id, title, note FROM content WHERE type = ? ORDER BY id DESC", + arrayOf(LIST_TYPE.toString()) + ) + cursor.use { c -> + val result = mutableListOf() + while (c.moveToNext()) { + val rowId = c.getInt(c.getColumnIndexOrThrow("id")) + val title = c.getString(c.getColumnIndexOrThrow("title")) + val note = c.getString(c.getColumnIndexOrThrow("note")) + result.add(WidgetList(rowId, title, parseItems(note))) + } + result + } + } + } catch (_: Exception) { + emptyList() + } + } + + /** Returns the list with the given id, or null if not found or deleted. */ + fun getListById(context: Context, id: Int): WidgetList? { + val path = getDbPath(context) + return try { + val db = SQLiteDatabase.openDatabase(path, null, SQLiteDatabase.OPEN_READONLY) + db.use { + val cursor = it.rawQuery( + "SELECT id, title, note FROM content WHERE id = ? AND type = ?", + arrayOf(id.toString(), LIST_TYPE.toString()) + ) + cursor.use { c -> + if (!c.moveToFirst()) return@use null + val rowId = c.getInt(c.getColumnIndexOrThrow("id")) + val title = c.getString(c.getColumnIndexOrThrow("title")) + val note = c.getString(c.getColumnIndexOrThrow("note")) + WidgetList(rowId, title, parseItems(note)) + } + } + } catch (_: Exception) { + null + } + } + + /** + * Resolves which list to display for a given widget instance. + * Uses the saved selection from SharedPreferences; falls back to the latest list + * if no selection is saved or if the saved list has been deleted. + */ + fun resolveList(context: Context, widgetId: Int): WidgetList? { + val savedId = WidgetPrefs.getSelectedListId(context, widgetId) + if (savedId != -1) { + val list = getListById(context, savedId) + if (list != null) return list + // Saved list was deleted — clear stale pref and fall back to latest + WidgetPrefs.clearSelectedListId(context, widgetId) + } + return getLatestList(context) + } + /** * Toggles the checked state of a single item at [itemIndex] within * the list identified by [listId], then persists the updated JSON. diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetListSelectActivity.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetListSelectActivity.kt new file mode 100644 index 0000000..62b2887 --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetListSelectActivity.kt @@ -0,0 +1,47 @@ +package com.pgarr.simplenotepad.widget + +import android.appwidget.AppWidgetManager +import android.os.Bundle +import androidx.appcompat.app.AlertDialog +import androidx.appcompat.app.AppCompatActivity +import com.pgarr.simplenotepad.R + +class WidgetListSelectActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val widgetId = intent.getIntExtra( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID + ) + if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + finish() + return + } + + val lists = WidgetDbHelper.getAllLists(this) + if (lists.isEmpty()) { + finish() + return + } + + val titles = lists.map { it.title }.toTypedArray() + + AlertDialog.Builder(this) + .setTitle(R.string.widget_select_list_title) + .setItems(titles) { _, index -> + val selected = lists[index] + WidgetPrefs.setSelectedListId(this, widgetId, selected.id) + val manager = AppWidgetManager.getInstance(this) + NoteListWidget.updateWidget(this, manager, widgetId) + manager.notifyAppWidgetViewDataChanged( + intArrayOf(widgetId), + R.id.widget_list_view + ) + finish() + } + .setOnCancelListener { finish() } + .show() + } +} diff --git a/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetPrefs.kt b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetPrefs.kt new file mode 100644 index 0000000..e7aada6 --- /dev/null +++ b/android/app/src/main/java/com/pgarr/simplenotepad/widget/WidgetPrefs.kt @@ -0,0 +1,24 @@ +package com.pgarr.simplenotepad.widget + +import android.content.Context + +object WidgetPrefs { + private const val PREFS_NAME = "widget_prefs" + private const val KEY_PREFIX = "selected_list_" + + fun getSelectedListId(context: Context, widgetId: Int): Int = + context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) + .getInt(KEY_PREFIX + widgetId, -1) + + fun setSelectedListId(context: Context, widgetId: Int, listId: Int) { + context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit() + .putInt(KEY_PREFIX + widgetId, listId) + .apply() + } + + fun clearSelectedListId(context: Context, widgetId: Int) { + context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE).edit() + .remove(KEY_PREFIX + widgetId) + .apply() + } +} diff --git a/android/app/src/main/res/drawable/widget_chevron_down.xml b/android/app/src/main/res/drawable/widget_chevron_down.xml new file mode 100644 index 0000000..fab973e --- /dev/null +++ b/android/app/src/main/res/drawable/widget_chevron_down.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/android/app/src/main/res/layout/widget_note_list.xml b/android/app/src/main/res/layout/widget_note_list.xml index ad37593..0048f22 100644 --- a/android/app/src/main/res/layout/widget_note_list.xml +++ b/android/app/src/main/res/layout/widget_note_list.xml @@ -8,17 +8,36 @@ android:padding="12dp" android:background="@drawable/widget_background"> - + android:orientation="horizontal" + android:gravity="center_vertical" + android:paddingBottom="8dp"> + + + + + + simple-notepad No items in this list + Select list contain false automatic diff --git a/android/app/src/main/res/values/styles.xml b/android/app/src/main/res/values/styles.xml index 45a97e6..d663dd1 100644 --- a/android/app/src/main/res/values/styles.xml +++ b/android/app/src/main/res/values/styles.xml @@ -11,4 +11,8 @@ @style/AppTheme icon_preferred + \ No newline at end of file diff --git a/app.json b/app.json index 56d659a..aeaea35 100644 --- a/app.json +++ b/app.json @@ -2,7 +2,7 @@ "expo": { "name": "simple-notepad", "slug": "simple-notepad", - "version": "1.4.4", + "version": "1.5.0", "orientation": "portrait", "icon": "./assets/images/icon.png", "scheme": "simple-notepad", @@ -24,7 +24,7 @@ "backgroundColor": "#ffffff" }, "package": "com.pgarr.simplenotepad", - "versionCode": 21 + "versionCode": 22 }, "web": { "bundler": "metro", @@ -43,7 +43,7 @@ "projectId": "9e3820b7-558b-4bd2-a1b2-e49561e741e6" } }, - "runtimeVersion": "1.4.4", + "runtimeVersion": "1.5.0", "updates": { "url": "https://u.expo.dev/9e3820b7-558b-4bd2-a1b2-e49561e741e6" } diff --git a/package-lock.json b/package-lock.json index 05ac08b..456f854 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "simple-notepad", - "version": "1.4.4", + "version": "1.5.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "simple-notepad", - "version": "1.4.4", + "version": "1.5.0", "dependencies": { "@react-navigation/native": "^7.0.0", "@rn-primitives/portal": "~1.3.0", diff --git a/package.json b/package.json index 7635a98..d1966a3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "simple-notepad", "main": "expo-router/entry", - "version": "1.4.4", + "version": "1.5.0", "scripts": { "prebuild": "expo prebuild", "dev": "expo start", From 89770a7e7ac3ff218dc06544e3118009fae639f9 Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Sun, 24 May 2026 21:21:53 +0200 Subject: [PATCH 2/3] Make widget chevron match primary text color Co-Authored-By: Claude Sonnet 4.6 --- android/app/src/main/res/drawable/widget_chevron_down.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/app/src/main/res/drawable/widget_chevron_down.xml b/android/app/src/main/res/drawable/widget_chevron_down.xml index fab973e..51e6c43 100644 --- a/android/app/src/main/res/drawable/widget_chevron_down.xml +++ b/android/app/src/main/res/drawable/widget_chevron_down.xml @@ -8,7 +8,7 @@ From 2c002b7ba1fbff3589b3a9ad017d4c581e978a7a Mon Sep 17 00:00:00 2001 From: Piotr Garlej Date: Sun, 24 May 2026 21:24:38 +0200 Subject: [PATCH 3/3] Require explicit ask before git ops and local device testing before push Co-Authored-By: Claude Sonnet 4.6 --- AGENTS.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/AGENTS.md b/AGENTS.md index 155836d..1331c54 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,6 +37,11 @@ Every change — no matter how small — must follow this exact sequence: Direct pushes to `master` are blocked by branch protection. CI enforces the version bump — a PR with the same version as `master` will fail. +**Agent rules:** + +- **Never create a branch, commit, or PR unless explicitly asked by the developer.** Make code changes when asked; git operations only on explicit instruction. +- **Do not push** until the developer has tested the changes locally on a dev device and confirmed they are ready. + Choose the bump type based on the nature of the changes: - **patch** (`npm run bump:patch`) — bug fixes, small tweaks, copy changes