Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
97 commits
Select commit Hold shift + click to select a range
2df5a56
ADFA-5067 | Add deep-link request models, path-traversal guard, and b…
davidschachterADFA Aug 10, 2026
6b96c84
ADFA-5067 | Add DeepLinkActivity as the sole App Link entry point
davidschachterADFA Aug 10, 2026
8c42c35
ADFA-5067 | Handle deep links with no project open in MainActivity
davidschachterADFA Aug 10, 2026
0df3845
ADFA-5067 | Handle deep links to an already-open project in EditorHan…
davidschachterADFA Aug 10, 2026
1109bf1
ADFA-5067 | Add RFC 5785 .well-known/assetlinks.json for App Links ve…
davidschachterADFA Aug 10, 2026
a0790b2
ADFA-5067 | Document the deep-link entry point in ARCHITECTURE.md
davidschachterADFA Aug 10, 2026
1078604
ADFA-5067 | Fix deep-link routing race in DeepLinkActivity
davidschachterADFA Aug 11, 2026
aea677b
ADFA-5067 | Guard MainActivity's deep-link handling against recreation
davidschachterADFA Aug 11, 2026
3e8fd65
ADFA-5067 | Prevent stacked confirm-close dialogs from dropping a dee…
davidschachterADFA Aug 11, 2026
045aa00
ADFA-5067 | Fix reserved-keyword collision in DeepLinkRequest.parse()
davidschachterADFA Aug 11, 2026
ab4be5e
ADFA-5067 | Close symlink escape in resolveWithinDirectory
davidschachterADFA Aug 11, 2026
3ad035b
ADFA-5067 | Sync ARCHITECTURE.md with the DeepLinkActivity routing fix
davidschachterADFA Aug 11, 2026
0f5b682
ADFA-5067 | Acquire RecentProjectDao through Koin, not a raw DB call
davidschachterADFA Aug 11, 2026
ee35586
ADFA-5067 | Show a Toast when a deep link fails to parse
davidschachterADFA Aug 11, 2026
4196a34
ADFA-5067 | Handle SecurityException scanning projects for a deep link
davidschachterADFA Aug 11, 2026
cc74e65
ADFA-5067 | Don't let a Recents-write failure crash the app
davidschachterADFA Aug 11, 2026
b68b50a
ADFA-5067 | Name deliberately-unused catch bindings "_"
davidschachterADFA Aug 11, 2026
45d94cd
ADFA-5067 | Add more reserved-keyword-collision regression cases
davidschachterADFA Aug 11, 2026
a451470
ADFA-5067 | Fix three deep-link close/open correctness gaps
davidschachterADFA Aug 11, 2026
df705c9
ADFA-5067 | Match line/column from the end of the path, not the start
davidschachterADFA Aug 11, 2026
de0e9e8
ADFA-5067 | Add embedded-keyword regression tests; use Truth in this …
davidschachterADFA Aug 11, 2026
86c1f70
ADFA-5067 | Block a new confirm-close while a save-and-close is in fl…
davidschachterADFA Aug 11, 2026
9741df7
ADFA-5067 | Remove dead saveProjectToRecents(); Koin-provide PendingD…
davidschachterADFA Aug 11, 2026
e9a1afb
ADFA-5067 | Look up a deep-linked project by name directly, not by sc…
davidschachterADFA Aug 11, 2026
f8cb2c9
ADFA-5067 | Deduplicate deep-link line/column parsing
davidschachterADFA Aug 11, 2026
11d1988
ADFA-5067 | Fix path traversal introduced by findValidProjectByName
davidschachterADFA Aug 11, 2026
a44feeb
ADFA-5067 | Narrow the Recents-insert catch to SQLException
davidschachterADFA Aug 11, 2026
6a92920
ADFA-5067 | Document MainViewModel's screen-state and event contracts
davidschachterADFA Aug 11, 2026
7e92715
ADFA-5067 | Dismiss the confirm-close dialog in onDestroy()
davidschachterADFA Aug 12, 2026
fa73614
ADFA-5067 | Always invoke saveAllAsync's runAfter, even if saveAll th…
davidschachterADFA Aug 12, 2026
2a9c28a
ADFA-5067 | Reject overlapping confirm-close requests instead of hija…
davidschachterADFA Aug 12, 2026
3b7afd7
ADFA-5067 | Fix same-project fast path; dedupe deep-link project lookup
davidschachterADFA Aug 12, 2026
dbf4f55
ADFA-5067 | Drain the pending file request even when sync fails
davidschachterADFA Aug 12, 2026
8eb75ca
ADFA-5067 | ActionContextProvider never hands back a finishing activity
davidschachterADFA Aug 12, 2026
06751ad
ADFA-5067 | Add CLEAR_TOP so repeated deep links don't stack MainActi…
davidschachterADFA Aug 12, 2026
df7d7b4
ADFA-5067 | Reject "." and embedded separators in a deep-link project…
davidschachterADFA Aug 12, 2026
8343ea9
ADFA-5067 | Widen the Recents-insert catch back to Throwable
davidschachterADFA Aug 12, 2026
de62fac
ADFA-5067 | Document the full deep-link routing/file-open flow
davidschachterADFA Aug 12, 2026
de7ad7f
Merge remote-tracking branch 'origin/stage' into task/ADFA-5067-deep-…
davidschachterADFA Aug 12, 2026
7a89bd6
ADFA-5067 | Use the inherited SLF4J logger, not android.util.Log
davidschachterADFA Aug 12, 2026
cfb708e
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 13, 2026
44d0dbc
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 13, 2026
49c0cd3
ADFA-5067: Validate deep-link scheme/host; fix silent line/column par…
davidschachterADFA Aug 14, 2026
8b8150f
ADFA-5067: Fix deep-link project-open/close race conditions from code…
davidschachterADFA Aug 14, 2026
232d249
ADFA-5067: Document the blank-projectDirPath branch in onNewIntent
davidschachterADFA Aug 14, 2026
17d29d1
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 14, 2026
e2e4f03
ADFA-5067: Reindent GitBottomSheetFragment.kt and IEditorHandler.kt t…
davidschachterADFA Aug 15, 2026
47a6eef
ADFA-5067: Fix third-round /code-review xhigh findings
davidschachterADFA Aug 15, 2026
dd21d62
ADFA-5067: Fix /code-review max findings
davidschachterADFA Aug 15, 2026
277435d
ADFA-5067: Fix second /code-review max findings pass
davidschachterADFA Aug 15, 2026
84bc0de
ADFA-5067: Fix CodeRabbit findings from the dd21d62b7 review round
davidschachterADFA Aug 15, 2026
696fc4e
ADFA-5067: Fix real findings from another /code-review max pass
davidschachterADFA Aug 15, 2026
b8e1c43
ADFA-5067: Fix CodeRabbit nitpicks from the 84bc0de35 review round
davidschachterADFA Aug 15, 2026
656a236
ADFA-5067: Fix test-isolation gap in the single-segment dot-dot test
davidschachterADFA Aug 15, 2026
3590380
ADFA-5067: Fix real findings from a third /code-review max pass
davidschachterADFA Aug 16, 2026
85877e2
ADFA-5067: Fix real findings from a fourth /code-review max pass
davidschachterADFA Aug 16, 2026
dcfeb90
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 16, 2026
40abc0a
ADFA-5067: Fix real findings from a fifth /code-review max pass
davidschachterADFA Aug 17, 2026
b162f58
Merge remote-tracking branch 'origin/task/ADFA-5067-deep-links' into …
davidschachterADFA Aug 17, 2026
5528e14
ADFA-5067: Fix real findings from a sixth /code-review max pass
davidschachterADFA Aug 17, 2026
cf4277c
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 17, 2026
8618ca6
ADFA-5067: Address remaining open CodeRabbit test nitpicks
davidschachterADFA Aug 17, 2026
44e4daa
Merge remote-tracking branch 'origin/task/ADFA-5067-deep-links' into …
davidschachterADFA Aug 17, 2026
6d9c8d9
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 18, 2026
6c9a7da
Merge remote-tracking branch 'origin/stage' into task/ADFA-5067-deep-…
davidschachterADFA Aug 21, 2026
0761657
ADFA-5067: Address hal-eisen-adfa's PR review findings
davidschachterADFA Aug 21, 2026
72a1042
ADFA-5067: Accept the apex domain for App Links, not just www
davidschachterADFA Aug 21, 2026
059400e
ADFA-5067: Keep the deep-link handoff alive through teardown, and rem…
davidschachterADFA Aug 22, 2026
8a2044b
ADFA-5067: Log the save-during-teardown branch instead of leaving it …
davidschachterADFA Aug 22, 2026
90662d9
ADFA-5067: Reject a dot-dot path segment, not any filename containing…
davidschachterADFA Aug 24, 2026
3bcce80
ADFA-5067: Run the save on the application scope, not the activity's
davidschachterADFA Aug 24, 2026
e939324
ADFA-5067: Stop exporting MainActivity, the deep-link handoff target
davidschachterADFA Aug 25, 2026
6ccad37
ADFA-5067: Do not let a deep link walk past setup
davidschachterADFA Aug 26, 2026
00739ce
ADFA-5067: Gate the deep link on the toolchain that is on disk, not t…
davidschachterADFA Aug 26, 2026
c151bac
Merge stage into task/ADFA-5067-deep-links
davidschachterADFA Aug 27, 2026
6f13637
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 27, 2026
337fde0
ADFA-5067: Don't race Environment.init() in the deep-link setup gate
claude Aug 28, 2026
9afb976
ADFA-5067: Arm a mid-sync same-project deep link for postProjectInit
claude Aug 28, 2026
811479c
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 28, 2026
581dcf5
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 28, 2026
9a6d6ff
Merge remote-tracking branch 'origin/stage' into task/ADFA-5067-deep-…
claude Aug 28, 2026
cc1fcaf
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 29, 2026
f4a6188
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Aug 29, 2026
4c13c35
Merge remote-tracking branch 'origin/stage' into task/ADFA-5067-deep-…
claude Aug 31, 2026
7994a29
Merge branch 'stage' into task/ADFA-5067-deep-links
davidschachterADFA Sep 1, 2026
f2a5a9e
ADFA-5067: Fix 15 review findings in the deep-link work
davidschachterADFA Sep 1, 2026
6cde5d1
style: spotless reformat of Locations.kt, no functional change
davidschachterADFA Sep 1, 2026
c371122
ADFA-5067: Fix 7 review findings in the deep-link lifecycle work
claude Sep 1, 2026
3b81a4d
Merge branch 'stage' into task/ADFA-5067-deep-links
claude Sep 1, 2026
887ee0a
ADFA-5067: Fix the remaining review findings, and correct two of my own
davidschachterADFA Sep 1, 2026
fd3b7bf
ADFA-5067: Fix the third review round, including four of my own misses
davidschachterADFA Sep 1, 2026
6d64348
ADFA-5067: Pin the launch-Intent entry by value, not by slot
hal-eisen-adfa Sep 2, 2026
e8bd904
ADFA-5067: Stop losing (and mis-committing) a confirmed project switch
hal-eisen-adfa Sep 2, 2026
bdca56e
ADFA-5067: Stop a link dying permanently on a failed or unverifiable …
hal-eisen-adfa Sep 2, 2026
151d540
ADFA-5067: Record deep-link arrivals and outcomes
hal-eisen-adfa Sep 2, 2026
cf937bf
ADFA-5067: Log a terminal deep-link outcome on the editor path too
hal-eisen-adfa Sep 2, 2026
993d8e3
Merge branch 'stage' into task/ADFA-5067-deep-links
hal-eisen-adfa Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@ Feature code layers as **UI → ViewModel → Repository → data source**, with

**EventBus is a deliberate side-channel.** Long-running, cross-module signals (build/install lifecycle, editor events) are broadcast via GreenRobot EventBus (`@Subscribe(threadMode = ThreadMode.MAIN)`) and the `eventbus-events` module's shared event types. Treat it as the integration bus *between* subsystems; don't use it to replace a ViewModel's own state inside a single screen.

**App Links enter through a UI-less trampoline, not `MainActivity` directly.** `DeepLinkActivity` (`app/src/main/java/com/itsaky/androidide/activities/DeepLinkActivity.kt`) is the sole `<intent-filter>` holder for `https://appdevforall.org/device/open/project/...` (and the identical `www` subdomain). It never renders anything — it parses the URI into a `DeepLinkRequest` (project name plus an optional file/line/column), checks whether an editor is already on screen (`ActionContextProvider.getActivity()`, the live `EditorHandlerActivity` tracker -- not `IProjectManager`'s `workspace`, which stays null for the whole duration of a Gradle sync even while the editor is already open), and routes to `MainActivity` (nothing open) or the live, `singleTask` `EditorActivityKt`/`EditorHandlerActivity` (a project is open — reused via `onNewIntent`), then finishes itself. This avoids a visible flash of `MainActivity`'s real UI when the actual destination is the already-running editor.

`EditorHandlerActivity.onNewIntent` then branches on `projectDirPath` (set as soon as a project starts opening) rather than `workspace` so the mid-sync case still matches correctly: **same project already open** — no project-wise work, just navigate to the requested file (`applyDeepLinkFileRequest`); **a different project is open** — the existing, unmodified `confirmProjectClose()` dialog runs (it also guards against a second confirm-close request overlapping a manual close or an in-flight save, and a *third* overlapping request supersedes the second's pending callback rather than being dropped), and only once the user actually confirms does an `onDestroy()`-triggered hand-off (`PendingDeepLinkOpen`, Koin-provided) start the new project — deliberately deferred to `onDestroy()`, not fired synchronously after `finish()`, so the new `PROJECT_PATH` can't race a `singleTask` re-delivery to the dying instance; `projectDirPath` **is still blank** — this instance never actually finished initializing a project (e.g. recreated after process death with no `PROJECT_PATH` extra), so `confirmProjectClose()` would silently no-op (`contentOrNull` is null); this case reuses the same `onDestroy()`-deferred hand-off instead of showing a close dialog for a project that was never really open; **nothing was open** — `MainActivity.openProject`/`EditorHandlerActivity.postProjectInit` apply the pending file request once the cold-opened project's sync succeeds.

`DeepLinkActivity`'s "is a live editor already on screen" check (`ActionContextProvider.getActivity()`) is itself a heuristic, not a guarantee: Android can still spin up a genuinely new `EditorActivityKt` instance instead of delivering to the live one via `onNewIntent`. `BaseEditorActivity.onCreate` is `EXTRA_KEY`'s only other reader on the editor side for exactly this case — it compares the deep link's requested project name against whatever project the new instance actually ends up holding (explicit `PROJECT_PATH` extra, restored `savedInstanceState`, or the process-wide `ProjectManagerImpl` singleton's last-loaded project) and, on a mismatch, bounces back to `MainActivity` with the deep link forwarded rather than silently continuing to build editor UI for the wrong project.

The optional file path is attacker-controllable (a URL segment), so it's resolved through `PathTraversal.resolveWithinDirectory`'s traversal/symlink guard rather than a bare `File` join, both when opening a file in the already-open project and when matching the requested project name to a directory under `Environment.PROJECTS_DIR` (`findValidProjectByName`).

## Module Structure

Strategy: **layer-and-subsystem based**, not feature-by-feature. The Gradle build has ~80 modules (`settings.gradle.kts`) plus three included composite builds. `app` is the integration point; the rest are libraries it composes.
Expand Down Expand Up @@ -100,7 +108,7 @@ These structural facts shape every module. Day-to-day build *commands* live in `

> **Persistence policy (authoritative):** new relational/queryable persistence uses **Room** (`@Entity` + DAO + `RoomDatabase` with explicit migrations, provided via Koin). Non-relational settings use the **filesystem/preferences (DataStore)**. **Raw SQLite is the exception, not the default** — see [ADR 0001](docs/adr/0001-prefer-room-for-persistence.md).
>
> **Recent Projects** is the reference example of the default: `app/src/main/java/com/itsaky/androidide/roomData/recentproject/` (`RecentProjectRoomDatabase`, `@Database version = 4` with migrations 1→4; `RecentProjectDao`; the `RecentProject` `@Entity` → table `recent_project_table`). It's provided via Koin in `di/AppModule.kt` and consumed by `MainViewModel`, `RecentProjectsViewModel`, `MainActivity`, `ProjectInfoBottomSheet`, and `ProjectCreationManager`.
> **Recent Projects** is the reference example of the default: `app/src/main/java/com/itsaky/androidide/roomData/recentproject/` (`RecentProjectRoomDatabase`, `@Database version = 4` with migrations 1→4; `RecentProjectDao`; the `RecentProject` `@Entity` → table `recent_project_table`). It's provided via Koin in `di/AppModule.kt` and consumed by `RecentProjectsViewModel`, `ProjectInfoBottomSheet`, and `ProjectCreationManager` directly, and by `MainActivity`/`EditorHandlerActivity` indirectly through `RecentProjectRepository` (`repositories/RecentProjectRepository.kt`) -- kept behind that interface, rather than injecting the DAO into those two Activities directly, per this section's own UI -> ViewModel -> Repository -> data source layering.
>
> **Raw SQLite is allowed only when** the database is prebuilt and opened read-only, the data is performance/allocation-critical and needs granular schema control, or the schema is shared across a process/component boundary. Current exceptions: symbol indexing (`lsp/indexing/SQLiteIndex.kt`), tooltips (`idetooltips/ToolTipManager.kt`), in-app/plugin help (`plugin-manager/.../documentation/PluginDocumentationManager.kt`), and documentation serving (`common/.../documentation/DocumentationContentSource.kt`, the one pipeline behind both the in-process WebView transport and `app/.../localWebServer/WebServer.kt`). The `androidx.room:*` strings in `editor`'s `GroovyAutoComplete` are autocomplete suggestions for the *user's* code, not CoGo persistence.
>
Expand Down
Loading
Loading