Skip to content
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ See **[ARCHITECTURE.md](ARCHITECTURE.md)** β€” the single source of truth for th

- **Avoid new dependencies** β€” the build almost certainly already has what's needed. Check `gradle/libs.versions.toml` and `build.gradle.kts` first.
- **Persistence:** prefer **Room** for relational data and the filesystem/preferences for settings; raw SQLite only for justified exceptions β€” see [ADR 0001](docs/adr/0001-prefer-room-for-persistence.md).
- **Don't treat a large binary asset's on-disk content as ground truth without checking its provenance first.** Run `git ls-files <path>` / `git check-ignore -v <path>`, and grep the build files for how it's provisioned, before relying on its current schema or row content. Several assets here (e.g. `assets/documentation.db`, and the SDK/bootstrap/Gradle zips alongside it) are `.gitignore`d and fetched by a Gradle task from an external URL (see the `Asset(...)` list in `app/build.gradle.kts`) β€” a locally-cached copy can be stale independent of git commit history and silently diverge from the maintained original.
- **Protect the two Android system bars** in any UI work: the top status bar (clock, notifications, status icons) and the bottom navigation bar (home, back, recents). Don't draw over or intercept them.
- **Plan and size before building.** Prefer **one PR per ticket/use case** β€” don't force-split a coherent change (splitting has its own overhead when later edits span the pieces). When a change is large, break it into **reviewable commits** β€” mechanical/refactor commits separate from behavioral ones β€” and offer review-by-commit. Treat ~500 LOC / ~10 files as a signal to reach for that commit structure, not a hard cap; the ceiling rises as LLM-assisted review matures. For staged multi-commit refactors (e.g. removing a dependency across many files/modules), order stages easiest-to-hardest and independently compile/test each stage (see Build & test's fast-iteration guidance) before moving to the next, so a failure is isolated to the stage that caused it.
- **Keep docs in step with code.** When you change code, update the docs that describe it in the same change β€” a module's `README.md`, `ARCHITECTURE.md`, or an ADR β€” so a doc never outlives the API it documents (see REVIEW.md, Code quality). If the doc fix is out of scope, file a ticket rather than let it drift.
Expand Down
5 changes: 5 additions & 0 deletions docs/process/learnings.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,11 @@
## Measuring a real before/after delta
- To measure an actual size/perf delta for a change (not just estimate it), use `git worktree add <path> <base-commit>`, build there, and diff the artifacts β€” avoids disturbing the current working tree or stashing.

## SQLite CLI scripting
- The sqlite3 CLI's `.system` dot-command can hit a content-dependent shell-parsing failure when a line chains multiple operators (`;`, `&&`, `||`, parentheses) β€” reproduces for some strings and not others, so it won't show up in a quick smoke test. Keep each `.system` line to one plain `command | pipe > file`.
- `.bail on` is required for a `BEGIN;...COMMIT;`-wrapped script to actually be atomic: without it, a mid-script SQL error prints to stderr but the script keeps going, including reaching the final `COMMIT`, which persists whatever succeeded before the error. `.bail` also can't see `.system` shell failures directly β€” if a step's success depends on a shell command's exit status, assert it in SQL (e.g. a temp table with a `CHECK` constraint) rather than relying on `.bail` to catch it.
- Don't write a `.system` command's output to a fixed, guessable filename directly under `/tmp` (CWE-377) β€” another local user could pre-plant a symlink there or race the write against your later read. Create an owner-only working directory instead (`rm -rf` it, then `mkdir -m 700` it β€” the mode is set atomically at creation, so there's no window where it's briefly wider), write everything under that, and remove it when done. `mkdir` itself can fail (e.g. another user recreates the path between the `rm -rf` and the `mkdir`) β€” that's a `.system` failure `.bail` won't catch either, so assert the directory's mode in SQL before trusting it, the same way you'd guard the Brotli step above. A fresh `mktemp -d` per run would be even better, but it doesn't fit this script shape: each `.system` line is its own subshell, so a path it generates can't be carried into later `.system`/`READFILE()` calls without writing it to another fixed, guessable file first.

## Kotlin LSP test harness
- Disposing the `KtLspTestEnvironment` in a unit test (`env.close()`, or `Disposer.dispose(env.project)`) throws `AssertionError: Write access is allowed inside write-action only`. IntelliJ requires model teardown to run inside a write action. This is why `KtLspTestRule`'s teardown has `env.close()` commented out as "fails in test cases". To dispose deterministically in a test, wrap it: `ApplicationManager.getApplication().runWriteAction { env.close() }`.
- The index/compilation environment lifecycle is racy: background `IndexWorker` coroutines call `PsiManager.findFile(project)` and will crash with `Project is already disposed` if the project is disposed before the workers are stopped. Always stop & join `KtSymbolIndex.close()` (and cancel related scopes) before `Disposer.dispose(...)`.
45 changes: 45 additions & 0 deletions docs/process/retrospective.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,50 @@
# Retrospective Log

## 2026-08-13 - ADFA-5088: individual Preferences/Plugin Manager tooltips + docdb SQL scripts

### Time Breakdown

| Started | Phase | πŸ‘€ Hands-On Time | πŸ€– Agent Time | Problems |
Comment thread
davidschachterADFA marked this conversation as resolved.
|---------|-------|-----------------|---------------|----------|
| Aug 12, 7:38am | Setup & research (branch, ticket, docdb schema + Preferences tag investigation via 2 background agents) | ▏ ~3m | β–ˆβ–ˆβ–ˆβ–ˆ 42m | |
| Aug 13, 5:12am | Implement fixup commits + fold in Plugin Manager screen (investigated via background agent, then implemented) | β–Œ ~5m | β–ˆβ–ˆβ–ˆβ–ˆβ–ˆ 45m | ⚠ mid-session scope addition |
| Aug 13, 6:01am | Architecture review + open PR + Jira update | ▏ ~1m | β–ˆβ–ˆβ–ˆ 28m | |
| Aug 13, 6:30am | Code review response β€” verified findings, discovered stale local DB, rewrote SQL scripts | β–‹ ~6m | β–ˆ 13m | ⚠ near-miss: caught mid-review only because the user pushed back |
| Aug 13, 6:49am | Fixups, ADFA-5121 follow-up ticket, wrap-up | β–Ž ~2m | β–‹ 7m | |
| Aug 13, ~7:00am | Second review round: fail-fast SQL fix (`.bail on` + guard table), validated against user-supplied real DB copies (est.) | β–Œ ~8m | β–ˆβ–ˆβ–ˆβ–ˆ 35m | ⚠ `.system` shell-parsing rabbit hole before finding the right fix |
| Aug 13, ~8:00am | Retro + 2 follow-up PRs (docdb doc gotchas, CLAUDE.md provenance rule) (est.) | β–Š ~10m | β–ˆβ–ˆβ–ˆ 25m | |

*(A ~20.9h overnight gap between the first two phases is excluded from the bars/percentages below as idle time, not work. The last two rows are estimated from context, not re-run through the transcript-analysis script.)*

### Metrics

| Metric | Duration |
|--------|----------|
| Total active wall-clock | ~4h |
| Hands-on | ~35 min (15%) |
| Automated agent time | ~195 min (85%) |
| Idle (overnight, between sessions) | ~20.9h (excluded above) |
| Retro analysis time | ~3 min (script run) + manual extension for later phases |

### Key Observations
- **The one real near-miss**: a SQL script was built and validated against `assets/documentation.db` β€” a 213MB file that's `.gitignore`d and downloaded by a Gradle task, not a committed repo asset. Its schema and content were treated as ground truth (including writing "confirmed via sqlite3" claims into the script's own header) without ever running `git ls-files`/`git check-ignore` on it. The local copy was stale; the real database already had curated production content for 5 of the tags the script was about to write to, which would have been silently overwritten. Caught only because the user independently checked the schema and pushed back.
- **Second review round found a related, second-order bug**: a `BEGIN;...COMMIT;` wrapper without `.bail on` doesn't actually give atomicity β€” verified empirically that a mid-script SQL error still lets `COMMIT` through with whatever succeeded before it. Fixed with `.bail on` plus a temp-table `CHECK` constraint that turns a silently-empty Brotli payload into a catchable SQL error.
- **A costly (but ultimately abandoned) detour**: significant time went into reverse-engineering a content-dependent shell-parsing failure in the sqlite3 CLI's `.system` dot-command (some strings triggered a dash syntax error, most didn't, with no clean single hypothesis found). The eventual fix sidestepped the problem entirely β€” kept `.system` lines simple and did the fail-fast check in SQL instead of shell chaining β€” rather than continuing to chase the CLI quirk's root cause.
- **Good pattern reinforced twice**: both times a bulk SQL rewrite was needed under time pressure, it was done via a small Python script parsing and regenerating the statements programmatically, rather than hand-editing 60+ lines β€” this avoided introducing new content errors while doing a structural change.
- **Real-world validation loop with the user**: the user independently ran the scripts against copies of the real database (`.save`, current, `.new`) and handed back concrete artifacts (file paths, MD5 comparison) rather than descriptions β€” this was more useful than any amount of scratch-DB testing alone, and surfaced that an earlier script version had already partially, successfully applied to the "before" copy.

### Feedback
**What worked:** Not directly stated this session β€” inferred from the user's engagement pattern (quick short replies, handing over real artifacts to check rather than describing them).
**What didn't:** "Don't make assumptions about large binary files. They may be maintained and updated outside the repository." (direct user feedback, in response to the stale-DB near-miss)

### Actions Taken

| Issue | Action Type | Change |
|-------|-------------|--------|
| No standing guidance against treating a large binary asset's on-disk content as ground truth without checking provenance | CLAUDE.md | Added a bullet to "Project-specific constraints": check `git ls-files`/`git check-ignore` and how an asset is provisioned before trusting its schema/content β€” generalizes beyond docdb to ~6 other gitignored, externally-fetched assets in `app/build.gradle.kts` |
| `documentation.db`-specific provenance and SQL-authoring gotchas (`.system` chaining, `.bail on` + guard-table pattern) not documented anywhere a future SQL-script author would find them | Doc | `docs/documentation-database.md` updated via PR #1666 (ADFA-5123): provenance warning in "Where it lives", new "Writing one-off SQL scripts against this database" subsection |
| Dead `UseSytemShell` preference (class never instantiated, underlying setting never read elsewhere) found while auditing for tooltip coverage | Ticket | Filed ADFA-5121 |

## 2026-07-24 - LeakCanary icon shrink (ADFA-4843), JAXP/PDF.js investigations (ADFA-1491/ADFA-3304), and full blankj:utilcodex removal (ADFA-4649)

### Time Breakdown
Expand Down
Loading