Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .github/workflows/update-libs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ jobs:
["icons-repository"]="icons-repository.cgp"
["rainbow-on-the-go"]="rainbow-on-the-go.cgp"
["ai-literacy-course"]="ai-literacy-course.cgp"
["flutter-template"]="flutter-template.cgp"
)
for module in "${!MAP[@]}"; do
src=$(ls "${module}/build/plugin/"*.cgp 2>/dev/null | head -n1)
Expand Down
12 changes: 11 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ Every plugin depends on two jars in the repo-root `libs/`:
- **`plugin-api.jar`** — the IDE-side API surface (`IPlugin`, `PluginContext`, `BuildStatusListener`, `IdeBuildService`, etc.). Each plugin uses it as `compileOnly` (provided by the IDE at runtime) AND as `buildscript classpath` so the Gradle plugin can resolve symbols at configuration time.
- **`gradle-plugin.jar`** — the Gradle plugin with id `com.itsaky.androidide.plugins.build`, applied by every plugin. It's the output of CoGo's `plugin-api/plugin-builder/` module (separate from CoGo's `gradle-plugin/` module, which is unrelated despite the name). It packages the compiled Android library into a `.cgp`.

Both jars are referenced via `../libs/*.jar`. **A plugin folder is not standalone in isolation** — copy `libs/` along if you move one elsewhere. When CoGo's API changes, refresh via the script above or the **Update libs from CodeOnTheGo** GitHub Action (which also commits the refreshed jars, cuts a release, and deploys `.cgp` files to the website).
There is also **one shared Gradle wrapper at the repo root** (`gradlew` + `gradle/wrapper/`). New plugins should use it — build them with `cd <plugin> && ../gradlew assemblePlugin` rather than bundling a per-plugin `gradlew`/`gradle/wrapper/` copy. (`flutter-template` follows this; most older plugins still carry their own local wrapper and can be migrated opportunistically.)

Both jars are referenced via `../libs/*.jar`. **Always use the repo-root `libs/` jars and the repo-root Gradle wrapper — never bundle per-plugin copies.** A plugin that ships its own `libs/plugin-api.jar` / `libs/gradle-plugin.jar` (e.g. copied from another plugin) can drift out of sync with the rest of the repo; point `build.gradle.kts` (`compileOnly`) and `settings.gradle.kts` (buildscript `classpath`) at `../libs/*.jar` and delete any local `libs/`. The root `plugin-api.jar` already carries the full API surface (including `IdeTemplateService`/`CgtTemplateBuilder`), so newer sub-APIs do not justify a local copy. **A plugin folder is not standalone in isolation** — copy the root `libs/` along if you move one elsewhere. When CoGo's API changes, refresh via the script above or the **Update libs from CodeOnTheGo** GitHub Action (which also commits the refreshed jars, cuts a release, and deploys `.cgp` files to the website).

### Plugin shape

Expand Down Expand Up @@ -80,12 +82,20 @@ Plugins that extract bundled assets on-device once (currently `ai-literacy-cours

**Any change to extraction OR post-extraction generation logic must bump `INSTALL_VERSION`.** Otherwise the change compiles and packages cleanly but has zero effect on existing installs — they keep the stale extracted tree, and it looks like "my fix didn't work" (costing a device round-trip). Bumping the constant forces a clean re-extract. On a device with a prior install, confirm the marker version changed (or wipe plugin data) before concluding a fix works — see Verification below.

### Template-installer plugins (Pebble `.cgt`)

Some plugins are headless template installers (`flutter-template`, `pebble-custom-function-template-installer`): on `activate()` they register project templates with `IdeTemplateService` (building each `.cgt` from Pebble `.peb` skeletons under `src/main/assets/templates/<Variant>/`), and unregister on `deactivate()`. The templates then appear on the New Project screen beside the core ones. The source-of-truth skeletons follow the pattern in `~/src/dev-assets/templates`.

**Pebble gotcha:** a bare `${{TAG}}` at end-of-line loses its trailing newline to Pebble's newline-trimming and merges with the next line (this silently produced invalid YAML by collapsing `name:` and `description:` into one line). Ensure a non-newline character follows `}}` — the convention is to quote the value, e.g. `name: "${{APP_NAME | lower}}"`.

## Verification

**`./gradlew assemblePlugin` succeeding is not verification** — it only proves the plugin compiles and packages. Real verification for these plugins is device-level: push the built `.cgp` to a connected emulator/device, install through CoGo's Plugin Manager, exercise the feature end-to-end, and observe the expected behavior (UI element appears, file written, build hook fires, DB row replaced, etc.).

If device verification isn't possible in-session, say so explicitly rather than calling the change verified. Build success is necessary but never sufficient — this applies especially to plugins that mutate IDE state (`documentation.db`, settings, filesystem, project structure).

**Launching CoGo via adb.** Do **not** launch with `monkey` or a bare LAUNCHER intent (`adb shell monkey -p com.itsaky.androidide …`) — debug builds bundle **LeakCanary**, which registers its own launcher activity, so the intent can open LeakCanary's "Leaks" screen or a disambiguation chooser instead of the IDE. Start the explicit component: `adb shell am start -n com.itsaky.androidide/.activities.SplashActivity`. When re-verifying a plugin **icon** change under the same plugin id, note that the Plugin Manager caches icons via Glide (`cache/image_manager_disk_cache`) keyed by path without mtime invalidation — the old icon persists until that cache is cleared (`adb root`, delete the dir, restart) or you install on a clean device.

## Adding a new plugin

1. Copy `random-xkcd/` — it's the canonical starting template (small but complete, includes the in-IDE help HTML pattern that submissions are expected to follow).
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ See the official [plugin documentation](https://www.appdevforall.org/codeonthego
| [`compose-preview/`](compose-preview/) | Renders Jetpack Compose `@Preview` functions on-device — no full app build or run. |
| [`ai-literacy-course/`](ai-literacy-course/) | Bundles Learn AI Anywhere's offline "Introduction to AI" course (26 videos + interactive activities) and plays it full-screen, fully offline. |
| [`layout-editor/`](layout-editor/) | Visual drag-and-drop editor for Android XML layouts. |
| [`flutter-template/`](flutter-template/) | Adds Flutter starter project templates (Basic, BLoC, Provider, GetX, Riverpod) to the New Project screen. |

## Building a plugin

Expand Down
9 changes: 8 additions & 1 deletion docs/process/learnings.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,20 @@ Cross-session gotchas, discoveries, and patterns worth not re-deriving.

- Pulling SQLite databases out of an emulator-installed app:
- First try `adb -s <device> shell run-as <pkg> cat databases/<file> > local.db`. This only works if the app's APK has `android:debuggable="true"`. Code On The Go's release builds do not, so `run-as` returns "package not debuggable" and writes that error string to your output file.
- On an emulator (root-capable), the fallback is `adb root` (one-time per boot) followed by `adb pull /data/data/<pkg>/databases/<file> .`. This bypasses the debuggable check entirely.
- On an emulator (root-capable), the fallback is `adb root` (one-time per boot) followed by `adb pull /data/data/<pkg>/databases/<file> .`. This bypasses the debuggable check entirely. Reach for `adb root` early when inspecting/clearing on-device app data — it's far more reliable than fighting `run-as`'s working-directory quirks.
- **Launching Code On The Go via adb — don't use `monkey`.** CoGo *debug* builds bundle **LeakCanary**, which registers its own launcher activity, so `adb shell monkey -p com.itsaky.androidide -c android.intent.category.LAUNCHER 1` (or any bare LAUNCHER intent) may open LeakCanary's "Leaks" screen or a disambiguation chooser instead of the IDE, and the IDE's own activities are not exported (direct `am start` on them is denied). Launch the explicit launcher component: `adb shell am start -n com.itsaky.androidide/.activities.SplashActivity`.
- **Plugin Manager icons are Glide-cached.** CoGo loads plugin icons through Glide's disk cache (`/data/data/com.itsaky.androidide/cache/image_manager_disk_cache`), keyed by path without mtime invalidation. Reinstalling a plugin under the same `plugin.id` with changed `icon_day/night.png` keeps showing the **old** icon (the on-disk extracted icon under `app_plugin_icons/<id>/` updates correctly, but the rendered bitmap is stale). Clear that cache dir (via `adb root`) and restart, or install on a clean device, to confirm an icon change. Template *thumbnails* are not affected — they're re-read from the `.cgt` each time.

## Plugin build & install gotchas

- **On-device install markers hide code changes.** `ai-literacy-course`'s `CourseInstaller` extracts its bundle once and gates it behind `.installed-v<INSTALL_VERSION>`; if the marker exists, extraction *and* `CourseShell.generate()` are skipped. A logic fix (e.g. lesson-item ordering) has zero on-device effect until `INSTALL_VERSION` is bumped — it looks like "the fix didn't work" and costs a device round-trip. Bump the version constant as part of any extraction/generation change.
- **`assemblePlugin` silently ships broken `.cgp`s when downloaded assets are missing.** Plugins with a `downloadAssets` task (`ai-literacy-course` → course ZIP + `pdfjs.zip`; `ndk-installer-plugin`) don't fetch those assets during a plain `assemblePlugin`, and there's no build-time warning — the missing asset only surfaces as a runtime failure on device (`Bundled asset not found: pdfjs.zip` → "Could not prepare the course"). Run `./gradlew downloadAssets assemblePlugin` (or `scripts/update-libs.sh`) and `unzip -l` the `.cgp` to confirm assets are present before handing it over.

## CoGo project templates (Pebble `.cgt`)

- **A bare `${{TAG}}` at end-of-line loses its trailing newline.** CoGo's Pebble renderer trims the newline after a standalone substitution tag, so a `.peb` line like `name: ${{APP_NAME | lower}}` renders as `name: myapp` immediately followed by the *next* line with no break — e.g. `name: myappdescription: ...`, which is invalid YAML and silently corrupts the generated `pubspec.yaml`. `assemblePlugin` and `unzip -l` show nothing wrong; it only surfaces when you generate a project on device. Fix: put the tag inside quotes or otherwise make it not the last token on the line — `name: "${{APP_NAME | lower}}"`. Applies to any generated file where a value must stay on its own line (YAML keys, etc.). Dart's own `$var`/`${expr}` are *not* Pebble tokens (`${` + letter ≠ `${{`), so Dart interpolation passes through untouched.
- **Register templates headlessly via `IdeTemplateService`, not by writing to `/sdcard`.** The correct way to add project templates is a UI-less `IPlugin` that, on `activate()`, builds a `.cgt` from bundled Pebble assets (`createTemplateBuilder(...).addStaticFromAssets(...).build(getPluginDirectory())`) and calls `registerTemplate(file)`; on `deactivate()` it `unregisterTemplate(name)` + deletes the staged file. Model: `pebble-custom-function-template-installer` / `flutter-template`. Writing generated files directly to `Environment.getExternalStorageDirectory()` breaks under scoped storage (`targetSdk` ≥ 30) — that was the defect in the original Flutter Template submission.

## PDF debugging without pdftotext

- When `pdftotext`/`mutool`/`qpdf` aren't installed and you need to read a PDF's /Title or /Author, the Info dictionary is often inside a flate-encoded object stream. Walk every `stream ... endstream` block, `zlib.decompress` each, and regex for `/(Title|Author|Subject|Creator|Producer|Keywords)\s*\(...\)` in the decoded bytes — and for hex-encoded `<FEFF...>` UTF-16BE strings. This recovered the Morelli & Walde authorship of `JavaJavaJava.pdf` in the bookshelf session.
41 changes: 41 additions & 0 deletions docs/process/retrospective.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,46 @@
# Retrospectives

## 2026-07-09 — Flutter Template plugin: review + rebuild on the CoGo template system (ADFA-3857, PR #44)

### Time Breakdown
| Started | Phase | 👤 Hands-On Time | 🤖 Agent Time | Problems |
|---------|-------|-----------------|---------------|----------|
| Jul 9 10:16pm | PR #43 review — `/plugin-review` on `pair`, post inline PR comments | █ 5m | ██ 12m | |
| Jul 10 1:35am | Scope + plan — Jira triage, dev-assets/Pebble study (3 Explore agents), AskUserQuestion, approved plan | ████ 20m | ████ 22m | |
| Jul 10 1:49am | Build — rebuild submission as headless `IdeTemplateService` installer, author 5 Pebble `.cgt` templates | ██ 12m | █████ 50m | ⚠ `pubspec name:` newline-trim bug (caught on device) |
| Jul 10 2:30am | Icons — Flutter day/night plugin icons + 5 template thumbnails (ImageMagick) | █ 6m | ███ 25m | ⚠ Glide plugin-icon disk cache showed stale PCF icon |
| Jul 10 2:44am | Verify + ship — device install/generate/substitute, commit, push, PR #44 | ██ 10m | ████ 22m | ⚠ LeakCanary hijacked `monkey` launch; emulator UI flakiness |
| Jul 10 4:15am | Retro + root consolidation — shared repo-root `libs/`+wrapper, docs | █ 5m | ██ 15m | |

### Metrics
| Metric | Duration |
|--------|----------|
| Total wall-clock (active, excl. two long idle gaps) | ~3h |
| Hands-on | ~1h (rough; the analyzer over-counts AskUserQuestion/plan text as typing) |
| Automated agent time | ~2h 20m |
| Idle/away (overnight + compaction gap) | ~16h |
| Retro analysis time | ~10 min |

### Key Observations
- **Rebuild-not-patch was the right call, and verifying the API first avoided PR #43's failure mode.** Ali's submission wrote hardcoded Dart to `/sdcard` (broken under scoped storage) because his bundled `plugin-api.jar` lacked `IdeTemplateService`. PR #43 had just failed by calling an *unreleased* API; here I confirmed `IdeTemplateService`/`CgtTemplateBuilder` exist in the **repo-root** `libs/` before building. Trusting the root jar (not a per-plugin copy) is what made the installer approach safe.
- **Device verification earned its keep — again.** `assemblePlugin` + `unzip -l` looked clean, but installing on the emulator and actually generating a project surfaced the `pubspec.yaml` `name:` corruption (Pebble trimmed the newline after a bare `${{APP_NAME | lower}}`, merging it into `description:`). This is the second consecutive session where build-success masked a real defect.
- **Two IDE-side quirks cost real time:** LeakCanary intercepting the `monkey` LAUNCHER intent (opened its Leaks screen instead of the IDE), and Glide's path-keyed plugin-icon disk cache showing the stale PCF icon after reinstall. Both are now documented so they're one-line fixes next time.
- **Explore agents front-loaded the design well.** Three parallel Explore agents (dev-assets pattern, Pebble mechanics, PCFInstaller shape) delivered the whole recipe before any code was written — the build phase had almost no false starts.

### Feedback
**What worked:** The plan-first approach (Explore agents → AskUserQuestion on the 5-variant/SDK scope → approved plan) meant the rebuild went cleanly. Device verification caught the pubspec bug.
**What didn't:** "i feel like leak canary slowed us down" — the `monkey`-launch detour into LeakCanary's UI was avoidable. Emulator UI-automation flakiness (ANRs, empty bounds) made the final device re-verify not worth it.

### Actions Taken
| Issue | Action Type | Change |
|-------|-------------|--------|
| LeakCanary hijacked `monkey` app-launch | CLAUDE.md + learnings.md | Documented launching via `am start -n com.itsaky.androidide/.activities.SplashActivity` (Verification §; learnings "Android / adb") |
| Pebble bare-tag newline-trim silently corrupts generated files | CLAUDE.md + learnings.md | Documented quoting values that must survive on their own line: `name: "${{APP_NAME \| lower}}"` (template-installer subsection; new learnings section) |
| Glide plugin-icon disk cache shows stale icon after reinstall | learnings.md (bug already tracked) | Documented cache-clear (`adb root` + rm `image_manager_disk_cache`). Underlying CoGo bug is already filed as **ADFA-4446** (Glide load in `PluginListAdapter.kt` lacks a content signature) — no new ticket needed |
| Per-plugin `libs/` and gradle-wrapper copies drift from repo | CLAUDE.md + code | Mandated repo-root shared `libs/` **and** a single repo-root Gradle wrapper; promoted the wrapper to root and pointed `flutter-template` at `../gradlew` / `../libs/*.jar`; deleted its local copies |

**Glide icon-cache bug — already tracked as [ADFA-4446](https://appdevforall.atlassian.net/browse/ADFA-4446)** (filed 2026-06-25). Root cause: `PluginListAdapter.kt` (~line 71) loads the extracted icon `File` with Glide and no cache signature, so `ObjectKey(File)` hashes the stable path and serves the old bitmap when content changes in place. Fix on file: `.signature(ObjectKey(iconFile.lastModified()))`. No new ticket was created; my session draft turned out to duplicate it.

## 2026-07-01 — Code review (PR #31) + AI Literacy Course ordering fix (PR #36)

### Time Breakdown
Expand Down
35 changes: 35 additions & 0 deletions flutter-template/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Gradle files
.gradle/
build/

# Local configuration file (sdk path, etc)
local.properties

# Log/OS Files
*.log
.DS_Store

# Android Studio generated files and folders
captures/
.externalNativeBuild/
.cxx/
*.aab
*.apk
output-metadata.json

# IntelliJ
*.iml
.idea/

# Keystore / signing material — never commit
*.jks
*.keystore
release.properties
signing.properties
keystore.properties

# Google Services
google-services.json

# Android Profiling
*.hprof
52 changes: 52 additions & 0 deletions flutter-template/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Flutter Template

A [Code on the Go](https://github.com/appdevforall/CodeOnTheGo) plugin that adds **Flutter starter
project templates** to the IDE's New Project screen, alongside the built-in core templates.

It contributes five templates, one per state-management approach:

| Template | State management |
|---|---|
| Flutter Basic | none (plain `setState`) |
| Flutter BLoC | `flutter_bloc` |
| Flutter Provider | `provider` |
| Flutter GetX | `get` |
| Flutter Riverpod | `flutter_riverpod` |

Each generates a small, idiomatic Flutter project (a counter app) with `pubspec.yaml`, `lib/`,
and `analysis_options.yaml`, substituting the app name and package name you enter in the New
Project dialog.

## How it works

The plugin is a headless installer. On `activate()` it registers each template with the IDE via
`IdeTemplateService`, building a `.cgt` from the Pebble skeletons bundled under
`src/main/assets/templates/<Variant>/`; on `deactivate()` it unregisters them and deletes the
staged files. There is no UI — the templates simply appear in **New Project** after the plugin is
enabled. This mirrors the `pebble-custom-function-template-installer` example.

## Building

This plugin uses the repo-root Gradle wrapper and shared `../libs/` jars — it has no local copies.
Build it from its own directory via the parent wrapper:

```sh
cd flutter-template
../gradlew clean assemblePlugin
```

The `.cgp` lands in `build/plugin/fluttertemplate.cgp`. Install it from inside Code on the Go via
the Plugin Manager, then open **New Project** to see the Flutter templates.

## Note on the Flutter SDK

This plugin **scaffolds project files only** — it does not install the Flutter/Dart SDK. Code on
the Go does not yet ship an on-device Flutter toolchain, so building or running a generated project
requires Flutter on a separate machine (or a future on-device SDK installer). Enter an app name
that is a valid Dart package identifier (lowercase, no spaces — underscores are fine); it is
lower-cased for `pubspec.yaml`'s `name:` field.

## Credit

Contributed by **Ali** via the Code on the Go community submission process, rebuilt on the IDE's
template system.
Loading