ADFA-3550: allow plugins to contribute Tier 3 documentation#1215
ADFA-3550: allow plugins to contribute Tier 3 documentation#1215Daniel-ADFA merged 6 commits intostagefrom
Conversation
Plugins can declare a Tier 3 docs asset path via the new DocumentationExtension.getTier3DocsAssetPath() override. On install, PluginDocumentationManager walks the plugin APK assets and inserts them into documentation.db under plugin/<pluginId>/. Removal and verify-and-recreate mirror the existing Tier 1/2 lifecycle and plug into PluginManager's install/uninstall hooks.
📝 WalkthroughRelease Notes: Tier 3 Documentation Support for PluginsNew Features
Implementation Details
|
| Cohort / File(s) | Summary |
|---|---|
Plugin API Extensions plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/DocumentationExtension.kt |
Added fun getTier3DocsAssetPath(): String? (default null) and clarified PluginTooltipButton.uri resolution semantics (relative → plugin/<id>/..., leading / → stripped absolute local path, :// URLs passthrough). |
Plugin Manager Core plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt |
Refactored load/unload flow: added apkPath to LoadedPlugin, always register fragment/service and persist loaded plugin, early-return on failed init, added activateLoadedPlugin and async documentation install steps; remove Tier‑3 docs on unload. |
Documentation Manager plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/PluginDocumentationManager.kt |
New suspend APIs for Tier‑3: installPluginTier3Documentation, removePluginTier3Documentation, verifyAndRecreateTier3Documentation, isPluginTier3DocumentationInstalled. Implements APK-scoped AssetManager walk, extension→content-type resolution, optional Brotli compression, 1 MiB chunking with terminator rows, transactional replace, and tooltip URI normalization. |
Compression Utility plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/BrotliCompressor.kt |
New internal singleton BrotliCompressor using brotli4j; compress(input: ByteArray): ByteArray (quality 11, window 24). |
Content Type Resolver plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/ExtensionToContentTypeResolver.kt |
New resolver caching extension→ContentTypeRow(id, compression), uses MimeTypeMap fallback and DB ContentTypes lookup; caches misses. |
Asset Walker plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/Tier3AssetWalker.kt |
New Tier3AssetWalker.walk(assets, root) yields Tier3Asset(relativePath, bytes) sequence; enforces 10 MiB cap and skips unreadable/oversized assets. |
Build / Gradle plugin-manager/build.gradle.kts, plugin-api/build.gradle.kts |
Added libs.brotli4j dependency; updated plugin-api Android/Kotlin JVM target settings (compileSdk 36, minSdk 28, Java/Kotlin target JVM 17). |
Sequence Diagram
sequenceDiagram
participant PM as PluginManager
participant DocM as PluginDocumentationManager
participant Assets as Tier3AssetWalker
participant Resolver as ExtensionToContentTypeResolver
participant Brotli as BrotliCompressor
participant DB as SQLite Database
PM->>DocM: verifyAndRecreateTier3Documentation(pluginId, plugin, apkPath)
DocM->>DocM: open APK-scoped AssetManager
DocM->>Assets: walk(assets, rootAssetPath)
Assets-->>DocM: Tier3Asset(relativePath, bytes) sequence
loop per Tier3Asset
DocM->>Resolver: resolve(db, extension)
Resolver->>DB: query ContentTypes by mime
DB-->>Resolver: ContentTypeRow(id, compression)
alt compression == "brotli"
DocM->>Brotli: compress(bytes)
Brotli-->>DocM: compressedBytes
end
DocM->>DB: insert Content rows (1MiB chunking, terminator row if needed)
end
DocM-->>PM: installation result (success/failure)
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~55 minutes
Possibly related PRs
- ADFA-3694: add day/night icon support for plugins #1213: touches PluginManager load flow and LoadedPlugin fields (similar core loading changes).
- ADFA-2867: Migrate plugin tooltips into documentation.db #1019: related changes to plugin documentation/tooltip installation flows and DB handling.
Suggested reviewers
- itsaky-adfa
- jatezzz
Poem
🐰 I hopped through APKs by moonlight’s grin,
Gathered assets, tucked them safe within,
Brotli hummed while bytes grew small,
Chunked and stored — I did it all,
A rabbit’s patch, now docs stand tall.
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Docstring Coverage | Docstring coverage is 35.71% which is insufficient. The required threshold is 80.00%. | Write docstrings for the functions missing them to satisfy the coverage threshold. |
✅ Passed checks (4 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title clearly and concisely summarizes the main change: enabling plugins to contribute Tier 3 documentation, which is the primary objective of the changeset. |
| Description check | ✅ Passed | The description is directly related to the changeset, explaining the new Tier 3 documentation feature, the asset path declaration mechanism, and how it integrates with the plugin lifecycle. |
| Linked Issues check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
| Out of Scope Changes check | ✅ Passed | Check skipped because no linked issues were found for this pull request. |
✏️ Tip: You can configure your own custom pre-merge checks in the settings.
✨ Finishing Touches
📝 Generate docstrings
- Create stacked PR
- Commit on current branch
🧪 Generate unit tests (beta)
- Create PR with unit tests
- Commit unit tests in branch
ADFA-3550
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.
There was a problem hiding this comment.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt (2)
205-217:⚠️ Potential issue | 🟠 MajorUse
apkPathwhen verifying docs for already loaded plugins.
LoadedPlugin.apkPathis added, butverifyDocumentationForLoadedPlugins()still only recreates Tier 1/2 docs. Afterdocumentation.dbis recreated, the public manual verify path will leave Tier 3 rows missing for already loaded plugins.Suggested fix
- val pluginsWithDocs = loadedPlugins.values + val loadedDocsPlugins = loadedPlugins.values .filter { it.plugin is DocumentationExtension } - .associate { it.manifest.id to it.plugin as DocumentationExtension } + val pluginsWithDocs = loadedDocsPlugins + .associate { it.manifest.id to it.plugin as DocumentationExtension } if (pluginsWithDocs.isNotEmpty()) { logger.info("Verifying documentation for ${pluginsWithDocs.size} plugins") val recreatedCount = documentationManager.verifyAllPluginDocumentation(pluginsWithDocs) + loadedDocsPlugins.forEach { loadedPlugin -> + documentationManager.verifyAndRecreateTier3Documentation( + loadedPlugin.manifest.id, + loadedPlugin.plugin as DocumentationExtension, + loadedPlugin.apkPath + ) + } if (recreatedCount > 0) { logger.info("Recreated missing documentation for $recreatedCount plugins") }Also applies to: 1266-1272
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt` around lines 205 - 217, verifyDocumentationForLoadedPlugins builds pluginsWithDocs only from it.plugin and ignores the newly added LoadedPlugin.apkPath, so verification recreates only Tier 1/2 docs and leaves Tier 3 missing; update the map construction in verifyDocumentationForLoadedPlugins to include each LoadedPlugin.apkPath (e.g., associate it.manifest.id to a data tuple/object containing both it.plugin as DocumentationExtension and it.apkPath) and then call documentationManager.verifyAllPluginDocumentation with that enriched payload (or adjust that method signature to accept apkPath alongside the DocumentationExtension); apply the same change to the other occurrence mentioned so both code paths pass apkPath when invoking verifyAllPluginDocumentation.
429-454:⚠️ Potential issue | 🟠 MajorSkip Tier 3 install when the documentation install hook declines.
verifyAndRecreateDocumentation()returnsfalsewhenonDocumentationInstall()vetoes installation, but Line 443 still proceeds with Tier 3 installation. Preserve the existing lifecycle contract by gating Tier 3 on the Tier 1/2 result.Suggested fix
+ var documentationReady = false try { val docResult = documentationManager.verifyAndRecreateDocumentation(manifest.id, plugin) + documentationReady = docResult if (docResult) { logger.info("Documentation verified/installed for plugin: ${manifest.id}") } else { logger.warn("Failed to verify/install documentation for plugin: ${manifest.id}") } } catch (e: Exception) { logger.error("Error verifying/installing documentation for plugin: ${manifest.id}", e) } + if (!documentationReady) { + return@launch + } + try { val tier3Result = documentationManager.verifyAndRecreateTier3Documentation( manifest.id, plugin, pluginApkPath )🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt` around lines 429 - 454, The code currently always calls documentationManager.verifyAndRecreateTier3Documentation(...) even when documentationManager.verifyAndRecreateDocumentation(...) returned false (which indicates onDocumentationInstall() vetoed install); update the coroutine block so that after calling verifyAndRecreateDocumentation(manifest.id, plugin) you only attempt verifyAndRecreateTier3Documentation(manifest.id, plugin, pluginApkPath) when docResult is true (preserve existing try/catch/logging), and when docResult is false skip the Tier 3 call and log a warning that Tier 3 was skipped due to the primary documentation install being declined; reference verifyAndRecreateDocumentation, verifyAndRecreateTier3Documentation, manifest, pluginApkPath, and DocumentationExtension to locate the code.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/DocumentationExtension.kt`:
- Around line 94-101: Documentation currently claims URIs containing "://" are
passed through unchanged, but ToolTipManager unconditionally prefixes stored
paths with "http://localhost:6174/", causing external URLs to break; fix by
changing the tooltip URL construction in ToolTipManager (the prefixing logic
around the code at/near line 133) to only prepend "http://localhost:6174/" when
the stored path is a local path (i.e., does not contain "://" and/or is not an
absolute server path), and update DocumentationExtension.kt wording to
explicitly state that only local/relative paths are prefixed while URIs
containing "://" are left unchanged.
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/PluginDocumentationManager.kt`:
- Around line 214-215: Reject dot-segment/traversal in asset-relative paths
before concatenating into basePath: validate asset.relativePath in
PluginDocumentationManager (where basePath is built and insertContentChunked is
called) by normalizing the path (e.g., with java.nio.file.Path#normalize or URI
resolution) and ensure it does not start with "/" or contain ".." segments or
resolve outside the intended "plugin/<pluginId>/" namespace; if validation
fails, throw/log and skip storage. Apply the same validation to the other
occurrence around the insertContentChunked call referenced (the block at the
other location mentioned) so both uses of asset.relativePath are protected.
- Around line 286-289: The rawQuery in PluginDocumentationManager.kt uses
"plugin/$pluginId/%" directly in a LIKE pattern so pluginId characters '%' or
'_' act as wildcards; escape pluginId before building the LIKE parameter (or
validate/enforce allowed characters) and use an ESCAPE character in the SQL so
the bound parameter is treated literally. Update the rawQuery call(s) (the one
shown and the similar occurrences around lines 314-320) to sanitize pluginId by
replacing '%' and '_' (and the chosen escape char) with escaped versions and
pass the escaped parameter plus an ESCAPE clause in the SQL, or alternatively
validate pluginId against an allowed alphabet before running the query.
- Around line 356-362: The Content insert can silently fail because
SQLiteDatabase.insert(...) returns -1 on constraint errors; after calling
db.insert("Content", null, values) in PluginDocumentationManager (the block that
builds ContentValues with path, content/blob, contentTypeId, languageId), check
the returned Long and if it equals -1 throw an exception (e.g., SQLiteException
or SQLException) with a message including the path so the surrounding
transaction is aborted/rolled back; this ensures path collisions or schema
constraint failures do not leave the transaction marked successful and missing
Tier 3 chunks.
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/Tier3AssetWalker.kt`:
- Around line 43-44: The code currently does val bytes = try {
assets.open(absolute).use { it.readBytes() } } which reads entire asset into
memory; replace this with a bounded streaming approach: implement a bounded
reader helper (e.g., readWithLimit or BoundedInputStream) and use
assets.open(absolute) to stream through the compressor/chunk insertion logic
instead of readBytes(), enforcing a per-file max size (and track a running total
cap for the whole install). If a file exceeds the per-file or total cap, abort
processing for that asset (log or throw a clear exception) so large plugin docs
cannot OOM the installer; update places referencing the bytes variable to accept
streamed/chunked input or to process chunks incrementally.
---
Outside diff comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`:
- Around line 205-217: verifyDocumentationForLoadedPlugins builds
pluginsWithDocs only from it.plugin and ignores the newly added
LoadedPlugin.apkPath, so verification recreates only Tier 1/2 docs and leaves
Tier 3 missing; update the map construction in
verifyDocumentationForLoadedPlugins to include each LoadedPlugin.apkPath (e.g.,
associate it.manifest.id to a data tuple/object containing both it.plugin as
DocumentationExtension and it.apkPath) and then call
documentationManager.verifyAllPluginDocumentation with that enriched payload (or
adjust that method signature to accept apkPath alongside the
DocumentationExtension); apply the same change to the other occurrence mentioned
so both code paths pass apkPath when invoking verifyAllPluginDocumentation.
- Around line 429-454: The code currently always calls
documentationManager.verifyAndRecreateTier3Documentation(...) even when
documentationManager.verifyAndRecreateDocumentation(...) returned false (which
indicates onDocumentationInstall() vetoed install); update the coroutine block
so that after calling verifyAndRecreateDocumentation(manifest.id, plugin) you
only attempt verifyAndRecreateTier3Documentation(manifest.id, plugin,
pluginApkPath) when docResult is true (preserve existing try/catch/logging), and
when docResult is false skip the Tier 3 call and log a warning that Tier 3 was
skipped due to the primary documentation install being declined; reference
verifyAndRecreateDocumentation, verifyAndRecreateTier3Documentation, manifest,
pluginApkPath, and DocumentationExtension to locate the code.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: efd117a4-05db-472f-9b46-e791b7796627
📒 Files selected for processing (7)
plugin-api/src/main/kotlin/com/itsaky/androidide/plugins/extensions/DocumentationExtension.ktplugin-manager/build.gradle.ktsplugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.ktplugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/BrotliCompressor.ktplugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/ExtensionToContentTypeResolver.ktplugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/PluginDocumentationManager.ktplugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/documentation/Tier3AssetWalker.kt
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`:
- Around line 475-482: The code launches fire-and-forget IO coroutines which let
installs and uninstalls race (e.g., the CoroutineScope(Dispatchers.IO).launch
block calling runDocStep and
documentationManager.verifyAndRecreateTier3Documentation), so make documentation
install/removal per-plugin serialized: add a manager-owned CoroutineScope
(instead of ad-hoc CoroutineScope(Dispatchers.IO)), store a per-plugin
synchronizer (a Mutex or a per-plugin Job keyed by pluginId) on PluginManager,
and use that synchronizer in installPluginDocumentationAsync (the runDocStep
calls and documentationManager.verifyAndRecreateDocumentation /
verifyAndRecreateTier3Documentation) and in the uninstall/remove docs path so
uninstall awaits any ongoing install before deleting rows; ensure you
acquire/release the Mutex or chain Jobs around the runDocStep blocks to
serialize operations for the same pluginId.
- Around line 426-431: A plugin loaded while disabled returns before calling
activateLoadedPlugin, so later enablePlugin only calls plugin.activate and skips
contribution registration (docs, Tier 3 docs, snippets, build actions) performed
in activateLoadedPlugin; fix by reusing the same activation path: either have
enablePlugin call activateLoadedPlugin(loadedPlugin) instead of only
plugin.activate(), or extract the contribution-registration logic from
activateLoadedPlugin into a shared helper (e.g., registerContributions or
registerPluginContributions) and invoke that helper from both
activateLoadedPlugin and enablePlugin (affecting code paths around
activateLoadedPlugin, enablePlugin, loadedPlugin, manifest and plugin.activate;
also apply same change to the similar block mentioned for lines 442-455).
- Around line 490-500: The runCatching around block() currently swallows
CancellationException in the .onFailure { e -> ... } handler; update the
.onFailure handler used with runCatching in PluginManager (the runCatching {
block() } / .onSuccess / .onFailure sequence) to detect if e is a
CancellationException (or has one as a cause) and re-throw it immediately before
logging, so coroutine cancellation propagates correctly and structured
concurrency is preserved.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 2e3aefa9-ddb3-4c93-b3f1-4a7364f72761
📒 Files selected for processing (2)
plugin-api/build.gradle.ktsplugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (2)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt (2)
501-516:⚠️ Potential issue | 🟡 MinorPropagate coroutine cancellation from doc steps.
runCatchingcatches cancellation and this handler logs/suppresses it. Re-throwCancellationExceptionbefore logging so doc verification remains cancellable.Minimal fix
+import kotlinx.coroutines.CancellationException + private suspend fun runDocStep( label: String, pluginId: String, block: suspend () -> Boolean ) { runCatching { block() } .onSuccess { result -> if (result) { logger.info("$label verified/installed for plugin: $pluginId") } else { logger.warn("Failed to verify/install $label for plugin: $pluginId") } } .onFailure { e -> + if (e is CancellationException) throw e logger.error("Error verifying/installing $label for plugin: $pluginId", e) } }Does Kotlin runCatching catch CancellationException, and should coroutine cancellation be rethrown from coroutine error handlers?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt` around lines 501 - 516, In runDocStep, do not swallow coroutine cancellation: detect and rethrow CancellationException before logging; update the onFailure handler (or replace runCatching with try/catch) inside the runDocStep suspend function so that if the caught exception is CancellationException it is rethrown immediately, otherwise log the error with logger.error("Error verifying/installing $label for plugin: $pluginId", e). Ensure you reference runDocStep and its onFailure handler (or the block invocation) when making the change.
491-498:⚠️ Potential issue | 🟠 MajorSerialize documentation install/removal for the same plugin.
Install and unload still launch independent IO coroutines. An uninstall can remove documentation rows first, then an in-flight install can recreate
plugin/<pluginId>/...rows after the CGP is deleted. Use a manager-owned scope plus a per-pluginMutex/Job, and make removal wait behind any in-flight install.Also applies to: 525-547
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt` around lines 491 - 498, The documentation install/uninstall coroutines in PluginManager are started directly with CoroutineScope(Dispatchers.IO).launch, causing races between install and uninstall; change PluginManager to use a manager-owned CoroutineScope and serialize per-plugin doc operations by tracking a per-plugin Mutex or Job map keyed by pluginId (e.g., a MutableMap<String, Mutex> or MutableMap<String, Job>) and wrap calls to runDocStep/ documentationManager.verifyAndRecreateDocumentation and verifyAndRecreateTier3Documentation so that an uninstall acquires or awaits the per-plugin lock/job before proceeding, and installs also acquire it while running runDocStep("documentation", pluginId) and runDocStep("Tier 3 docs", pluginId) to ensure removals wait behind in-flight installs and vice versa.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`:
- Around line 461-483: When activation or subsequent registration fails, roll
back any partial contributions: track whether you registered a snippet (via
PluginSnippetManager.getInstance().registerPlugin), documentation
(installPluginDocumentationAsync), and build actions
(PluginBuildActionManager.getInstance().registerPlugin /
registerManifestActions) during the runCatching block, and in the onFailure
handler explicitly undo them (call
PluginSnippetManager.getInstance().unregisterPlugin(manifest.id), remove/cleanup
documentation via the corresponding uninstall/cleanup routine for
installPluginDocumentationAsync, and call
PluginBuildActionManager.getInstance().unregisterPlugin(manifest.id) and/or
remove manifest actions for manifest.id) before setting loadedPlugin.isEnabled =
false and savePluginState(manifest.id, false); also attempt to call any plugin
deactivation API if available (e.g., plugin.deactivate()) to ensure no active
state remains.
---
Duplicate comments:
In
`@plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt`:
- Around line 501-516: In runDocStep, do not swallow coroutine cancellation:
detect and rethrow CancellationException before logging; update the onFailure
handler (or replace runCatching with try/catch) inside the runDocStep suspend
function so that if the caught exception is CancellationException it is rethrown
immediately, otherwise log the error with logger.error("Error
verifying/installing $label for plugin: $pluginId", e). Ensure you reference
runDocStep and its onFailure handler (or the block invocation) when making the
change.
- Around line 491-498: The documentation install/uninstall coroutines in
PluginManager are started directly with CoroutineScope(Dispatchers.IO).launch,
causing races between install and uninstall; change PluginManager to use a
manager-owned CoroutineScope and serialize per-plugin doc operations by tracking
a per-plugin Mutex or Job map keyed by pluginId (e.g., a MutableMap<String,
Mutex> or MutableMap<String, Job>) and wrap calls to runDocStep/
documentationManager.verifyAndRecreateDocumentation and
verifyAndRecreateTier3Documentation so that an uninstall acquires or awaits the
per-plugin lock/job before proceeding, and installs also acquire it while
running runDocStep("documentation", pluginId) and runDocStep("Tier 3 docs",
pluginId) to ensure removals wait behind in-flight installs and vice versa.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 909b37bb-f5f9-43fd-bd94-aa0989db9a3a
📒 Files selected for processing (1)
plugin-manager/src/main/kotlin/com/itsaky/androidide/plugins/manager/core/PluginManager.kt
Plugins can declare a Tier 3 docs asset path via the new DocumentationExtension.getTier3DocsAssetPath() override. On install, PluginDocumentationManager walks the plugin CGP assets and inserts them(after brotlli compresssing) into documentation.db under plugin//. Removal and verify-and-recreate mirror the existing Tier 1/2 lifecycle and plug into PluginManager's install/uninstall hooks.
Screen_recording_20260420_074857.webm