Skip to content

Commit 8b68d3e

Browse files
committed
docs: refresh cycle 11 research queue
1 parent 58bb849 commit 8b68d3e

3 files changed

Lines changed: 142 additions & 2 deletions

File tree

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Cycle 11 Findings - 2026-06-04
2+
3+
## Scope
4+
5+
- Repository: `SwiftFloris`
6+
- Baseline: clean detached worktree at pushed `master` `31cfa44`
7+
(`docs: refresh cycle 10 research queue`), described as
8+
`v1.8.237-1-g31cfa44`.
9+
- Sync: `git pull --rebase origin master` reported up to date before this
10+
cycle.
11+
- Constraint: research/docs only. No feature source, tests, build files, or
12+
assets were edited.
13+
14+
## Anti-Duplicate Checks
15+
16+
- Did not duplicate R2-1. R2-1 handles synchronous exceptions caught by
17+
`FlorisApplication.onCreate()` before Settings installs the splash keep
18+
condition; this cycle covers failures inside the launched
19+
`FlorisApplication.init()` coroutine after that precheck has already run.
20+
- Did not duplicate R10-1. R10-1 scopes editor content-generation jobs to an
21+
active input session; this cycle scopes app preference-store initialization
22+
to a recoverable startup state.
23+
- Did not propose a generic coroutine-scope refactor. The target is the
24+
preference-init path that gates Settings UI rendering through
25+
`preferenceStoreLoaded`.
26+
- Left broader native-library logging and other low startup polish items for a
27+
later cycle; they do not cause the splash wait to hang.
28+
29+
## Local Evidence
30+
31+
- `FlorisApplication.kt:82` creates `CoroutineScope(Dispatchers.Default)`
32+
without a `SupervisorJob`.
33+
- `FlorisApplication.kt:161-170` launches `FlorisPreferenceStore.initAndroid`
34+
and sets `preferenceStoreLoaded.value = true` only after successful
35+
initialization and logging. There is no `try/catch`, `finally`, or failure
36+
state in that coroutine.
37+
- `FlorisApplication.kt:155-158` stages only synchronous exceptions thrown
38+
before `init()` returns.
39+
- `FlorisAppActivity.kt:102-115` checks for already-staged startup exceptions
40+
before installing the splash keep condition
41+
`!appContext.preferenceStoreLoaded.value`.
42+
- `FlorisAppActivity.kt:155-167` defers `setContent` until
43+
`preferenceStoreLoaded` becomes true.
44+
- `FlorisAppActivity.kt:313-319` consumes only pre-existing staged exceptions,
45+
so an async failure after the initial check does not automatically reopen the
46+
crash-dialog path.
47+
- `StartupCrashRecoveryTest.kt:50-78` covers staged exception persistence and
48+
redirect behavior, but it does not simulate a failing preference initializer.
49+
- `docs/AUDIT_2026-05-28.md:127-129` records this async preference-init failure
50+
as distinct from the synchronous staged-startup exception issue.
51+
52+
## Roadmap Changes Fed
53+
54+
- R11-1: Guard async preference-store init failures before the splash wait.
55+
Implementation should make the preference-init coroutine supervised and
56+
error-guarded, log and route failures to a recovery surface or deliberately
57+
degraded startup state, and guarantee the Settings splash condition cannot
58+
wait forever on `preferenceStoreLoaded == false`.
59+
60+
## Non-Adds
61+
62+
- No source fix was made in this cycle.
63+
- No new permission, storage, or network row added.
64+
- No product decision proposed. The user-visible behavior is recovery from a
65+
failed startup prerequisite, not a new setting or workflow.

RESEARCH_REPORT.md

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,14 @@
11
# SwiftFloris Research Report
22

3-
This report summarizes current research conclusions. The full 2026-05-25 research plan is archived at `docs/archive/research/RESEARCH_FEATURE_PLAN_2026-05-25.md`. Deep-research pass refreshed **2026-06-03** (post-v1.8.204), with 2026-06-04 freshness notes through Cycle 10.
3+
This report summarizes current research conclusions. The full 2026-05-25 research plan is archived at `docs/archive/research/RESEARCH_FEATURE_PLAN_2026-05-25.md`. Deep-research pass refreshed **2026-06-03** (post-v1.8.204), with 2026-06-04 freshness notes through Cycle 11.
4+
5+
2026-06-04 Cycle 11 note: after the Cycle 10 docs push, `master` is clean at
6+
`31cfa44` (`v1.8.237-1-g31cfa44`). Cycle 11 rechecked the async preference
7+
initialization audit against live `FlorisApplication.init()` and Settings
8+
splash code. R2-1 already handles synchronous staged startup exceptions, but
9+
the launched `initAndroid(...)` path can still leave `preferenceStoreLoaded`
10+
false forever. This cycle adds R11-1: guard preference-store init failures
11+
before the splash wait can hang indefinitely.
412

513
2026-06-04 Cycle 10 note: after the Cycle 9 docs push, `master` is clean at
614
`99a8431` (`v1.8.234-1-g99a8431`). Cycle 10 rechecked the deferred editor
@@ -140,6 +148,7 @@ Top opportunities (one line each):
140148
22. **User-dictionary blocked-back feedback** — active dictionary save/delete/import/export work now surfaces operation-specific feedback when system back is blocked (R8-1). [Closed]
141149
23. **Suggestion privacy request snapshot** — async candidate generation now freezes incognito/editor sensitivity and suggestion preference inputs before provider, trace, and ghost-text work runs (R9-1). [Closed]
142150
24. **Editor content-generation lifecycle** — delayed start/selection content jobs can still publish state and touch a captured `InputConnection` after reset/finishInput (R10-1, P2). [Verified]
151+
25. **Preference-store init splash recovery** — async `initAndroid` failures can leave `preferenceStoreLoaded` false after the staged-crash precheck already ran (R11-1, P2). [Verified]
143152

144153
No Critical or Major reliability/security defects were found that are not already on the roadmap or in the deferred audit lists. The remaining heavy work (glide model training, Vosk addon, F-Droid submission, device-only visual verification) stays maintainer-gated as the existing roadmap records.
145154

@@ -204,6 +213,11 @@ Privacy-first multilingual IME. `:app` is Apache-2.0-ceiling, no network permiss
204213
progress cards. v1.8.232 closes R8-1 by adding blocked-back feedback for the
205214
same save/delete/import/export work without weakening the leave-blocking
206215
policy. [Closed]
216+
- **Startup preference loading (partial):** R2-1 persists synchronous staged
217+
startup exceptions before the splash wait, but `FlorisApplication.init()` still
218+
runs preference-store loading inside an unguarded launched coroutine. R11-1
219+
prevents async preference-init failures from leaving Settings on the splash
220+
indefinitely. [Verified]
207221
- Established surfaces (autocorrect/SymSpell, glide classifier, clipboard, addons, voice handoff, sync, MCP, hardware-keyboard import) are covered by `COMPLETED.md` and the audits; no net-new gap surfaced beyond what the roadmap already tracks.
208222

209223
## Competitive Landscape
@@ -235,6 +249,9 @@ Privacy-first multilingual IME. `:app` is Apache-2.0-ceiling, no network permiss
235249
- **[Closed v1.8.237] Search highlight lifecycle** → RA-9. `SettingsSearchScreen.kt` marks `SettingsSearchHighlightStore.activeTarget`, while `FlorisScreen.kt` now consumes matching targets once into local state and exposes a close action so stale cards do not persist across later visits.
236250
- **[Closed v1.8.224] Search result scroll reset** → RA-10. `SettingsSearchScreen` now scrolls populated non-blank result sets back to item 0 when the query changes, guarded by `SettingsSearchScreenStateTest`.
237251
- **[Closed v1.8.218] Staged startup exception is never surfaced** → R2-1. `CrashUtility.consumeStagedException(...)` now persists the staged report without the process-killing handler, and `FlorisAppActivity` opens the crash dialog before installing the splash-screen keep condition.
252+
- **[Medium] Async preference-store init recovery** → R11-1. Guard the
253+
launched `FlorisPreferenceStore.initAndroid(...)` path so failure is logged,
254+
routed to recovery, and cannot leave `preferenceStoreLoaded` false forever.
238255
- **[Closed v1.8.219] Remaining diagnostic `printStackTrace()` paths** → R2-2. `RestoreScreen` failure diagnostics now use `flogError`, restore UI copy falls back to the existing "Unknown error" string for null/blank throwable messages, and `CrashUtility.writeToFile` logs through `LogTopic.CRASH_UTILITY`.
239256
- **[High] Local release ledger drift** → R3-1. Three code-fix commits after
240257
the v1.8.225 docs marker are untagged and absent from the release ledger.
@@ -310,6 +327,11 @@ Privacy-first multilingual IME. `:app` is Apache-2.0-ceiling, no network permiss
310327
ordering and adds immutable request inputs for provider calls, typing traces,
311328
and ghost-text gating, so delayed work does not re-read live incognito or
312329
editor-info state after the async boundary.
330+
- **Startup async boundary:** `FlorisAppActivity` correctly checks staged
331+
synchronous startup failures before its splash keep condition, but
332+
`FlorisApplication.init()` has no equivalent failure state for the launched
333+
preference-store load. R11-1 should make that async boundary supervised and
334+
observable.
313335
- **User-dictionary navigation policy:** `UserDictionaryEntryPolicy` correctly
314336
centralizes leave/mutation/transfer gates. v1.8.232 keeps that policy and
315337
adds a visible response when Compose back handling blocks the gesture during
@@ -322,7 +344,7 @@ Privacy-first multilingual IME. `:app` is Apache-2.0-ceiling, no network permiss
322344

323345
## Security / Privacy / Data Safety
324346

325-
No net-new permission or data-egress finding. The settings-search additions are display/navigation only; the no-results Browse all settings action (RA-2), synonym keyword coverage (RA-3), and query-change scroll reset (RA-10) do not weaken the no-network posture. R2-1 and R2-2 closed as local diagnostic-safety work without adding network, telemetry, or broad file export. R3-2 is also local-only clipboard filtering. R3-3 closed as sync-crypto contract hardening before transport activation, with no new permission or native dependency. R4-1/R4-2/R4-3/R4-4 are local correctness/a11y/API-contract work. R5-1 closed as trust-boundary hardening for optional addon APKs: it keeps the no-network addon screen but requires explicit trust before non-co-signed packages become active. R6-1 is local editor critical-section hardening and does not change storage, permissions, or outbound data. R7-1 closed as privacy posture hardening for the existing incognito mode and `FLAG_SECURE` contract, not a permission change. R9-1 is privacy-state hardening for existing local suggestion and smart-compose paths: it keeps the no-network posture and ensures `IME_FLAG_NO_PERSONALIZED_LEARNING` / incognito decisions are request-scoped across async work. R10-1 is local editor-session lifecycle hardening and does not change storage, permissions, or outbound data. R8-1 is UI feedback for an already-blocked dictionary operation path and does not change data retention, dictionary mutation, or export/import permissions. WS13 now explicitly includes the deferred `StickerMediaProvider.openFile` SAF allow-list validation so forged encoded sticker URIs are rejected without broadening file access. The deferred audit lists (`docs/AUDIT_2026-06-02.md`) remain the authority for crypto/parsing/lifecycle hardening; this pass does not duplicate them.
347+
No net-new permission or data-egress finding. The settings-search additions are display/navigation only; the no-results Browse all settings action (RA-2), synonym keyword coverage (RA-3), and query-change scroll reset (RA-10) do not weaken the no-network posture. R2-1 and R2-2 closed as local diagnostic-safety work without adding network, telemetry, or broad file export. R11-1 is the remaining async side of startup diagnostics: it should surface preference-store init failures without adding storage, permissions, or outbound data. R3-2 is also local-only clipboard filtering. R3-3 closed as sync-crypto contract hardening before transport activation, with no new permission or native dependency. R4-1/R4-2/R4-3/R4-4 are local correctness/a11y/API-contract work. R5-1 closed as trust-boundary hardening for optional addon APKs: it keeps the no-network addon screen but requires explicit trust before non-co-signed packages become active. R6-1 is local editor critical-section hardening and does not change storage, permissions, or outbound data. R7-1 closed as privacy posture hardening for the existing incognito mode and `FLAG_SECURE` contract, not a permission change. R9-1 is privacy-state hardening for existing local suggestion and smart-compose paths: it keeps the no-network posture and ensures `IME_FLAG_NO_PERSONALIZED_LEARNING` / incognito decisions are request-scoped across async work. R10-1 is local editor-session lifecycle hardening and does not change storage, permissions, or outbound data. R8-1 is UI feedback for an already-blocked dictionary operation path and does not change data retention, dictionary mutation, or export/import permissions. WS13 now explicitly includes the deferred `StickerMediaProvider.openFile` SAF allow-list validation so forged encoded sticker URIs are rejected without broadening file access. The deferred audit lists (`docs/AUDIT_2026-06-02.md`) remain the authority for crypto/parsing/lifecycle hardening; this pass does not duplicate them.
326348

327349
## UX & Accessibility
328350

@@ -346,6 +368,8 @@ The keyboard surface already has a strong a11y baseline (`ACCESSIBILITY.md`, `To
346368
request-boundary contract is covered by focused JVM tests.
347369
5. R10-1 needs fake/delayed editor-session tests around reset/finishInput; no
348370
maintainer product decision is required.
371+
6. R11-1 needs a forced failing preference initializer in tests or debug smoke;
372+
no maintainer product decision is required.
349373

350374
## Archived Evidence
351375

@@ -378,3 +402,4 @@ The keyboard surface already has a strong a11y baseline (`ACCESSIBILITY.md`, `To
378402
- Cycle 9 external source classes checked: Android `EditorInfo` /
379403
`IME_FLAG_NO_PERSONALIZED_LEARNING`.
380404
- Cycle 10 companion: `.ai/research/2026-06-04/CYCLE_10_FINDINGS.md`.
405+
- Cycle 11 companion: `.ai/research/2026-06-04/CYCLE_11_FINDINGS.md`.

ROADMAP.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,56 @@ These are genuine blockers — each needs an account, key, sibling repo, ML infr
161161

162162
## Research-Driven Additions
163163

164+
### Researcher Queue (Cycle 11 - 2026-06-04)
165+
166+
- [x] 🔬 `prefs-init-splash-recovery-recheck-2026-06-04` - synced
167+
`master` after the Cycle 10 docs push, rechecked the older async preference
168+
initialization audit against live startup code after v1.8.218 closed only the
169+
synchronous staged-exception path. This cycle adds one focused row for
170+
preference-store failures that happen inside `FlorisApplication.init()`'s
171+
launched coroutine.
172+
173+
#### Startup recovery
174+
175+
- [ ] 🤖 P2 — Guard async preference-store init failures before the splash wait (R11-1)
176+
- Why: `FlorisApplication.onCreate()` now stages and surfaces synchronous
177+
startup exceptions, but `init()` still launches `FlorisPreferenceStore`
178+
initialization on a plain background scope and flips
179+
`preferenceStoreLoaded` only after that suspend call succeeds. If
180+
`initAndroid(...)` throws or the plain parent job fails, the Settings
181+
activity keeps the splash screen because its keep condition waits on the
182+
same false flow value, and the existing staged-startup crash redirect has
183+
already run. This is separate from R2-1: the failure happens after
184+
`onCreate()` returns and outside the synchronous `try/catch`.
185+
- Evidence: `FlorisApplication.kt:82` creates
186+
`CoroutineScope(Dispatchers.Default)` without a `SupervisorJob`;
187+
`FlorisApplication.kt:161-170` launches `FlorisPreferenceStore.initAndroid`
188+
and sets `preferenceStoreLoaded.value = true` only after logging the
189+
successful result; there is no `try/catch`, `finally`, or failure state in
190+
that coroutine; `FlorisAppActivity.kt:102-115` checks only already-staged
191+
startup exceptions before installing the splash keep condition
192+
`!appContext.preferenceStoreLoaded.value`; `FlorisAppActivity.kt:155-167`
193+
defers `setContent` until that flow becomes true; `FlorisAppActivity.kt:313-319`
194+
consumes only pre-existing staged exceptions; `StartupCrashRecoveryTest.kt:50-78`
195+
covers staged exception persistence/redirects but not async preference
196+
initialization failure; the deferred audit tracks this distinct path in
197+
`docs/AUDIT_2026-05-28.md:127-129`.
198+
- Touches: `FlorisApplication.kt` and focused startup tests, likely by
199+
extracting a small preference-init helper or injectable initializer so
200+
tests can force an `initAndroid` failure without corrupting real datastore
201+
files. Keep the existing R2-1 staged-crash behavior intact.
202+
- Acceptance: the preference-init coroutine uses a supervised/error-guarded
203+
scope; failures are logged and routed to an existing crash/recovery surface
204+
or a deliberately degraded startup state; `preferenceStoreLoaded` cannot
205+
remain false indefinitely after init failure; Settings does not keep the
206+
splash forever; tests simulate a failing preference initializer and verify
207+
crash/recovery visibility plus splash unblock behavior.
208+
- Verify: `./gradlew.bat :app:testDebugUnitTest --tests
209+
"dev.patrickgold.florisboard.app.*Startup*"` plus a manual debug smoke with
210+
a forced preference-init failure confirming Settings leaves the splash and
211+
shows the intended recovery path.
212+
- Complexity: M
213+
164214
### Researcher Queue (Cycle 10 - 2026-06-04)
165215

166216
- [x] 🔬 `editor-content-job-lifecycle-recheck-2026-06-04` - synced

0 commit comments

Comments
 (0)