summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--MEMORY.md8
-rw-r--r--feedback_delegation_and_context_protection.md29
-rw-r--r--feedback_feedreader_alarm_vs_explicit_refresh.md17
-rw-r--r--feedback_terse_manager_updates.md15
-rw-r--r--feedback_user_does_not_read_code.md35
-rw-r--r--feedback_verify_from_exit_codes_not_output.md57
-rw-r--r--project_doot_bucket_followups.md48
-rw-r--r--project_doot_pull_punch_list.md36
-rw-r--r--project_feedreader.md15
-rw-r--r--project_feedreader_apk_publishing.md47
-rw-r--r--project_feedreader_emulator.md14
-rw-r--r--project_feedreader_feature_candidates.md57
12 files changed, 375 insertions, 3 deletions
diff --git a/MEMORY.md b/MEMORY.md
index f66be96..c64e9a6 100644
--- a/MEMORY.md
+++ b/MEMORY.md
@@ -1,14 +1,20 @@
# Memory Index
+- [Delegation & context protection](feedback_delegation_and_context_protection.md) — fork vs fresh vs inline decision rule; `~/.claude/agents/verifier.md` custom agent for adversarial claim-checking without redoing work
+- [Terse manager-level updates](feedback_terse_manager_updates.md) — status updates should be outcome+next-step only, not sub-fix technical narratives
+- [User doesn't read the code](feedback_user_does_not_read_code.md) — never cite commits/diffs as evidence to Peter; explain in observable behavior, and own correctness since he won't catch errors by review
- [Doot production deployment](project_doot_prod.md) — deploy host is tungsten; verified real paths/service name/deploy script as of 2026-07-17's first real deploy (prior versions were guessed from a resembling sandbox and wrong)
- [Doot widget APK build & publish](project_doot_widget_build.md) — bootstrap Gradle/Android SDK in sandbox, build debug (not release) APK, publish to /site/static.terst.org/public/files/doot-widget.apk
- [Always pull before building](feedback_pull_before_build.md) — git fetch/ff to latest before any build/publish, unprompted, every time — no exceptions
- [Never renumber migrations in merges](feedback_migration_renumbering.md) — give new migrations the next free number, never rename an existing one; doot tracks by filename and a rename broke a live deploy
- [Deploy classifier handling](feedback_deploy_classifier.md) — how to work with the auto-mode gate on deploy-shaped actions without trying to route around it; this user's single-user-infra attitude toward deploy caution
+- [Doot pull-based punch list](project_doot_pull_punch_list.md) — committed_date model shipped 2026-09-03; ask Peter about carry-over on/after ~2026-09-10 if he hasn't raised it
+- [Doot bucket follow-ups](project_doot_bucket_followups.md) — wants a Home Maintenance bucket + on-demand pull; plus an unreported bug where active bucket items never return to dormant
- [Doot "marine" project code map](reference_doot_marine_codemap.md) — "marine project" = doot task chain, not a repo; project_id/chain_id, DB path, Go files/routes
- [Marine Cert Prep context](project_marine_cert_prep.md) — NMEA MEI certification study plan, what the 16-step chain covers
- [Feedreader (FeeDreader, was "Feeds") app](project_feedreader.md) — greenfield Android RSS reader, org.terst.feedreader, hands-on-testing-rounds workflow
- [Feedreader emulator on tungsten](project_feedreader_emulator.md) — feedreader_test AVD for direct testing/debugging instead of phone-only feedback
+- [Feedreader APK publishing](project_feedreader_apk_publishing.md) — he installs from static.terst.org, not from sent files; stale publishes cause a Room downgrade crash (data survives)
- [Feedreader APK naming](feedback_feedreader_apk_naming.md) — send debug builds as feeds-debug.apk, not the default app-debug.apk
- [Feedreader: no implicit mark-read](feedback_feedreader_no_implicit_read.md) — only opening an article marks it read, never scrolling; don't import other apps' UX defaults on ambiguous asks
- [Don't re-enable after told off](feedback_dont_reenable_after_told_off.md) — when told to leave something off, verify OFF is respected; don't flip it on yourself to "keep testing"
@@ -16,6 +22,7 @@
- [Widget color theming iteration](feedback_widget_color_theming.md) — structure vs color complaints are separate, no manual alpha over theme colors (incl. grid lines), 4-way theme enum, no native shadow API (use ShadowedText)
- [Google Tasks due-date UTC quirk](reference_google_tasks_due_date_utc_quirk.md) — API's date-only due field is always midnight-UTC serialized; don't `.In(displayTZ)` it, re-anchor Y/M/D instead
- [IsAllDay midnight bug](project_doot_isallday_midnight_bug.md) — FIXED 2026-08-10 (9a3ece4): dated tasks landed in widget's undated pool (shown as "today"); original fix plan would've broken web rendering, actual fix used a new Undated field instead
+- [Verify from exit codes, not output](feedback_verify_from_exit_codes_not_output.md) — use /workspace/devtools wrappers; grepped console text has produced three wrong claims
- [Verify before asserting root cause](feedback_verify_before_asserting_root_cause.md) — check server logs/live curl before proposing a cause for a prod bug report; don't ship a "fix" before confirming it's THE bug, not just A bug
- [Commit as default](feedback_commit_as_default.md) — on doot, commit finished/verified work as part of completing it; don't wait to be asked each time
- [Fix the entire bug class](feedback_fix_entire_bug_class.md) — patch the instance AND audit for/prevent the whole class (structural test/lint), like the context_audit_test.go example
@@ -40,3 +47,4 @@
- [Claudomator builder prompt decomposition bias](project_claudomator_builder_prompt_decomposition_bias.md) — retro hypothesis: bias builder's prompt toward decomposing more readily; not yet implemented
- [Claudomator Tasks tab tree view TODO](project_claudomator_tasks_tab_tree_view_todo.md) — replace flat Tasks tab with a live tree view rooted at stories; not yet implemented
- [Claudomator distributed execution idea](project_claudomator_distributed_execution_idea.md) — reuse NativeRunner per remote machine + escalation-ladder round-robin; check agentloop tool parity first
+- [Feedreader refresh model](feedback_feedreader_alarm_vs_explicit_refresh.md) — keep the 5am alarm + fetch-on-launch + explicit button; don't over-apply "no background triggers" to kill it too
diff --git a/feedback_delegation_and_context_protection.md b/feedback_delegation_and_context_protection.md
new file mode 100644
index 0000000..383fe81
--- /dev/null
+++ b/feedback_delegation_and_context_protection.md
@@ -0,0 +1,29 @@
+---
+name: feedback-delegation-and-context-protection
+description: "How to decide fork vs fresh subagent vs do-it-inline, and how to verify subagent claims without redoing their work — grounded in Anthropic's own published multi-agent guidance, adopted 2026-08-24 after a jacoco/Gradle debugging spiral ate a large chunk of a live conversation"
+metadata:
+ node_type: memory
+ type: feedback
+ originSessionId: 9dfd7445-41b2-47af-a522-0538bd22ed74
+ modified: 2026-08-24T07:47:20.793Z
+---
+
+The user wants me to act more like an engineering lead: keep my own context focused on product direction, catching drift, and verifying quality — not grinding through deep implementation/debugging inline. They explicitly want one interface (me), not a choice between "talk to Claude" or "talk to an implementor" — I decide per-task whether to just do it or delegate, and report outcomes, not process.
+
+**Why:** Triggered live by a concrete failure: a Gradle/KSP toolchain bug (adding detekt+jacoco to the FeeDreader Android project broke `kspDebugKotlin`) turned into ~20+ tool calls of inline trial-and-error debugging in the main conversation before the user interrupted to say this should have been forked off. They also flagged, from earlier the same session, that a research subagent had confidently mis-called a real lint bug a "false positive" — I only caught it because I happened to verify independently. Both are the same underlying problem: work that should be isolated (deep exploration, or claims that need checking) was instead either done inline or trusted without a structured check.
+
+**How to apply — delegation:**
+- Do it inline only when the task is bounded: a few tool calls, clear scope, low risk of turning into multi-round trial-and-error.
+- **Fork** (not fresh) when work needs my accumulated context but its blow-by-blow isn't worth keeping — deep debugging, multi-step research building on what's already been established in the conversation. Rule of thumb: the moment a subproblem is about to become open-ended trial-and-error (roughly 3-4+ rounds of "try something, see if it worked"), stop and fork it instead of continuing to grind inline. Forks share the parent's prompt cache, so they're cheap to spin up mid-task.
+- **Fresh subagent** (general-purpose, or a named custom type) when the task is genuinely independent of the current conversation — external research, a cleanly-specced fix/feature that doesn't need my accumulated debugging context. Cheaper to brief fresh than to inherit noise.
+- **Workflow tool**: reserved for genuinely large/parallel/repeatable orchestration (dozens+ of agents, codified multi-phase pipelines) — not a default. Still gated on explicit user opt-in per existing tool rules; this preference doesn't change that gate.
+- Don't over-architect: Anthropic's own postmortems (see below) warn that elaborate multi-role pipelines (planner/executor/reviewer/iterator with handoffs) lose fidelity at every handoff and often underperform a single well-prompted agent, or one lead + one verifier. Two clean roles (lead, verifier) beats inventing a cast of named agents for this scale of work (two personal hobby codebases).
+- Real cost: Anthropic reports multi-agent setups typically cost 3-10x+ the tokens of doing it single-agent. Delegate because isolation/parallelism/specialization genuinely pays off for that specific task, not as a reflexive default.
+
+**How to apply — verification without redoing the work:**
+- A custom `verifier` agent type now exists at `~/.claude/agents/verifier.md` (user-scoped, applies to any project): fresh context (deliberately NOT a fork — a verifier should evaluate the artifact on its own terms, not inherit the generator's reasoning/bias), read-mostly tools (no Edit/Write), explicitly instructed to try to refute the claim rather than confirm it, and required to cite specific checks performed rather than a vague "looks right."
+- Invoke it for consequential claims specifically: a crash-risk fix, a security-relevant change, a disputed audit finding, anything that will be trusted downstream without me personally re-deriving it. Not needed for routine mechanical work.
+- Escalate to doing it myself when: verifiers disagree, the claim is high-blast-radius (crash/security/data-loss), or it touches the standing rules/process itself (e.g. a project's CLAUDE.md).
+- Anthropic's own stated failure mode to watch for: a verifier asked only "does this look good?" will rubber-stamp. Every verification task needs an explicit, checkable criterion, not an open-ended vibe check.
+
+**Sources grounding this** (see [[project-feedreader]] and [[project-doot-prod]] for the projects this applies across): Anthropic's "Building Effective Agents," "How we built our multi-agent research system," "Effective context engineering for AI agents," "When to use multi-agent systems (and when not to)," "Multi-agent coordination patterns," and Claude Code's own "Best Practices" doc (subagents for investigation, adversarial review step in fresh context, "if you can't verify it, don't ship it").
diff --git a/feedback_feedreader_alarm_vs_explicit_refresh.md b/feedback_feedreader_alarm_vs_explicit_refresh.md
new file mode 100644
index 0000000..9c5f72f
--- /dev/null
+++ b/feedback_feedreader_alarm_vs_explicit_refresh.md
@@ -0,0 +1,17 @@
+---
+name: feedback-feedreader-alarm-vs-explicit-refresh
+description: "feedreader refresh model is alarm + fetch-on-launch + explicit button, not alarm-vs-explicit-only"
+metadata:
+ node_type: memory
+ type: feedback
+ originSessionId: d248310c-6c0c-43d3-8ae2-1e765347624b
+ modified: 2026-08-22T22:42:36.554Z
+---
+
+When the user asked for "an explicit refresh button and only refresh on fresh open or explicit refresh," I removed the 5am morning alarm (MorningFetchReceiver) along with the hourly periodic WorkManager job. The user immediately corrected: they wanted the periodic job gone but the alarm kept — "I want the alarm... might want to know if there are new articles without screwing up the list ordering, but that's less important."
+
+**Why:** the user's phrasing wasn't a literal spec ("only these two triggers") — it was really about killing the noisy/unpredictable hourly polling, not the specific once-a-day alarm they'd deliberately asked for earlier in the same session. Don't over-apply a stated constraint to every mechanism that superficially fits the same category ("background trigger") when only one instance of it was actually the complaint.
+
+**How to apply:** feedreader's refresh model is now: fetch on cold launch, fetch via the explicit refresh button (Unread/Older top bar), and the 5am exact alarm — no periodic hourly job. If asked to touch refresh behavior again, keep the 5am alarm unless told otherwise.
+
+Also flagged, lower priority, not yet implemented: a background/alarm fetch arriving while the Unread list is open can reorder visible rows (new articles insert by publishedAt DESC). The existing "N new articles" snackbar already surfaces the count; the ask is to also avoid the reorder/jump, but the user called this less important than getting the alarm back.
diff --git a/feedback_terse_manager_updates.md b/feedback_terse_manager_updates.md
new file mode 100644
index 0000000..34d2922
--- /dev/null
+++ b/feedback_terse_manager_updates.md
@@ -0,0 +1,15 @@
+---
+name: feedback-terse-manager-updates
+description: "Status updates should be much terser now that the user has moved me into an engineering-manager role — outcomes and decisions only, not sub-fix technical narratives"
+metadata:
+ node_type: memory
+ type: feedback
+ originSessionId: 9dfd7445-41b2-47af-a522-0538bd22ed74
+ modified: 2026-08-24T08:10:37.066Z
+---
+
+Even after adopting the fork/delegate-more-of-the-work model (see [[feedback_delegation_and_context_protection]]), my status updates to the user were still "kinda cluttered" — e.g. recounting a sub-fix's root cause and verification steps ("turned out to be the ben-manes plugin, I independently re-verified...") when the outcome ("fixed, verified, moving on") was all that was needed.
+
+**Why:** The user explicitly framed this as a "promotion" (2026-08-24) — I should operate at a higher altitude in *communication*, not just in *execution*. Delegating the work but then reporting it back in full technical detail defeats half the point: their context/attention is the same resource my own context window is, and cluttered updates spend it the same way a cluttered context window does.
+
+**How to apply:** Default status-update shape: what changed (1 line), what's next (1 line). Drop root-cause narratives, verification play-by-play, and file/line specifics from chat updates unless: the user asks for detail, it's a genuine decision point needing their input, or something is surprising/risky enough to flag on its own merits. The detailed trail still belongs in commit messages and code comments (already standard practice here) — that's the right home for it, not the conversation. This applies most when reporting on delegated/forked work, where the temptation is to relay everything the subagent/fork reported back, but it applies to my own inline work too.
diff --git a/feedback_user_does_not_read_code.md b/feedback_user_does_not_read_code.md
new file mode 100644
index 0000000..14792be
--- /dev/null
+++ b/feedback_user_does_not_read_code.md
@@ -0,0 +1,35 @@
+---
+name: feedback-user-does-not-read-code
+description: "Peter does not read the code, diffs, or commit messages on his projects — he only experiences the running app, so explanations must be behavioral and correctness is entirely on me"
+metadata:
+ node_type: memory
+ type: feedback
+ originSessionId: dc4bccb0-7454-412c-9fcd-f9e336f62a74
+ modified: 2026-09-03T07:44:32.910Z
+---
+
+Peter stated plainly (2026-09-03, doot): **"I don't look at your commits though, the
+code is entirely yours."** He experiences these projects only as a user of the
+running app — not through diffs, commit messages, file paths, or symbol names.
+
+**Why:** I had answered "do I need a new APK?" by showing `git log` output for
+`android/` and comparing commit timestamps. That's evidence he has no use for; the
+answer was just "no." Worse, it implicitly treats commits as a shared artifact when
+they're write-only from his side.
+
+**How to apply:**
+- Answer in terms of *observable behavior* ("the widget picks this up on its own",
+ "you'll see it after a refresh"), never "see commit abc123" or "the diff in
+ `native_tasks.go`". Detailed rationale still belongs in commit messages and code
+ comments — that's their real audience: future me, not him.
+- Don't cite code or commits as proof to *him*. If something needs proving, prove
+ it against live behavior/data and report the result.
+- **Correctness is entirely mine.** He will not catch a bad change by reviewing it,
+ so "he'll spot it if I'm wrong" is never a valid fallback — verify against real
+ data before saying something works (see
+ [[feedback_verify_before_asserting_root_cause]]).
+- I own tracking what changed and what that implies for him (e.g. whether a rebuild
+ or reinstall is needed) — he shouldn't have to derive it.
+- Pairs with [[feedback_terse_manager_updates]]: that one says report at a higher
+ altitude; this one says the altitude is *user-visible behavior*, not a simplified
+ summary of the code.
diff --git a/feedback_verify_from_exit_codes_not_output.md b/feedback_verify_from_exit_codes_not_output.md
new file mode 100644
index 0000000..ead8323
--- /dev/null
+++ b/feedback_verify_from_exit_codes_not_output.md
@@ -0,0 +1,57 @@
+---
+name: feedback-verify-from-exit-codes-not-output
+description: "Never conclude a build/test/launch result from grepped console text — use /workspace/devtools wrappers, which report from exit codes and structured artifacts"
+metadata:
+ node_type: memory
+ type: feedback
+ originSessionId: 08c1f89c-4d24-4fb2-9488-0fe155872388
+ modified: 2026-09-18T21:05:18.382Z
+---
+
+**Never report a build, test, or launch result based on grepped console output.**
+Use the shared wrappers, invoked by absolute path:
+
+```
+/workspace/devtools/bin/gradle-verify <project-dir> <task...>
+/workspace/devtools/bin/gradle-test <project-dir> [task] -- --rerun-tasks
+/workspace/devtools/bin/android-launch-check <package> [--apk <path>] [--settle <s>]
+```
+
+**Why:** in one session (2026-09-18) I reported three results that were about
+something other than what I believed I was testing, and Peter noticed:
+
+1. A failed `cd` in a chained command left the shell in the previous directory;
+ the `BUILD SUCCESSFUL` I then reported was for a different project tree.
+2. `adb shell monkey` + a single `pidof` sample reported a healthy app as crashed
+ and a crashed app as healthy, on the same emulator minutes apart.
+3. `adb install` refused a filename (not ending `.apk`) and said so on stdout,
+ which I had redirected to `/dev/null`; the previous build stayed installed and
+ was what actually got tested.
+
+`gradle-test` caught a fourth immediately on first real use: nav reporting
+`UP-TO-DATE` with 33 stale result files, which read naively as "241 tests pass"
+without a single test having run.
+
+**Shell traps that cause this class of error** (all encoded in the wrappers, all
+worth avoiding in ad-hoc commands too):
+
+- `cmd | tail; echo $?` reports `tail`'s status. Redirect to a file and check
+ `$?`, or use `${PIPESTATUS[0]}`.
+- `... | grep -q` under `set -o pipefail` SIGPIPEs the upstream command when grep
+ exits early on a match — the pipeline reports failure *because* it matched. Do
+ membership tests in pure shell (`case`).
+- `set -e` plus `pid=$(pidof x)` aborts the script exactly when the process is
+ gone, i.e. when you are trying to detect a crash. Use `|| true` on any
+ substitution whose non-zero status is meaningful data.
+- Gradle `UP-TO-DATE` means results on disk describe older code. Pass
+ `-- --rerun-tasks` whenever the number matters.
+- A green build with zero tests executed is not a passing suite.
+
+**How to apply:** when stating a result to Peter, the number must come from a
+fresh parsed artifact, not from console text. If a claim can't be backed that
+way, say so rather than asserting it. He does not read the code, so an
+unverified claim is one he cannot catch.
+
+See `/workspace/devtools/README.md` for the full trap table. Referenced from
+nav's and feedreader's `CLAUDE.md`. Related: [[project_feedreader_apk_publishing]]
+(the `am start` vs `monkey` detail), [[feedback_verify_before_asserting_root_cause]].
diff --git a/project_doot_bucket_followups.md b/project_doot_bucket_followups.md
new file mode 100644
index 0000000..608f3e3
--- /dev/null
+++ b/project_doot_bucket_followups.md
@@ -0,0 +1,48 @@
+---
+name: project-doot-bucket-followups
+description: "doot maintenance-bucket follow-ups requested 2026-09-07: a Home Maintenance bucket, on-demand pull from a bucket, plus an unreported bug where active bucket items never return to dormant"
+metadata:
+ node_type: memory
+ type: project
+ originSessionId: dc4bccb0-7454-412c-9fcd-f9e336f62a74
+ modified: 2026-09-07T07:08:00.017Z
+---
+
+Captured 2026-09-07 as explicit "note for later" items, none of them built yet.
+Bucket mechanics live in `internal/store/buckets.go`; the pull model they plug
+into is [[project_doot_pull_punch_list]].
+
+**1. A "Home Maintenance" bucket.** Peter wants a second bucket alongside Yard
+Work. Seed item he named: "fix ohana curtain holder" (this already exists as a
+free-standing undated task — fold it in rather than duplicating). Contents beyond
+that need asking; don't invent household chores for him the way the yard list was
+gathered (he supplied every yard item himself).
+
+**2. Pull from a bucket on demand.** Today a bucket only surfaces work on its
+cycle (Yard Work: pick 2 every 4 days). He wants to be able to say "give me
+something from Home Maintenance right now" without waiting for the cycle. Likely
+shape: an action that activates the next-stalest pool item immediately —
+`selectBucketCycle` already ranks by staleness, so this is close to a
+one-item-now variant of it.
+
+**3. Bug he has NOT reported — active items never go dormant.**
+`selectBucketCycle` activates `pick_n` dormant items but nothing ever deactivates
+an active one that simply wasn't done. An item leaves `active` only by being
+completed or deferred, so actives accumulate: 7 were active on 2026-09-07 against
+a `pick_n` of 2, including three left over from the old 14-day cycle. Left alone
+this drains the pool back to "everything is always active", which is exactly the
+condition populating the pool was meant to fix.
+
+**Why this is a question, not a fix:** the obvious repair (uncompleted actives
+return to dormant at the next cycle) mirrors his stated staleness rule for
+recurring tasks — "if I don't get a weekly task done by the next iteration it's
+just stale/abandoned" — but that's an inference about intent, and inferring intent
+from a symptom is precisely what went wrong on the recurrence fix earlier in that
+session. Ask before changing rotation semantics. See
+[[feedback_investigate_the_actual_claim]].
+
+**Also relevant:** a bucket cycle stamps every item it activates with a
+byte-identical `due_date`, which is why two chores landed at the exact same
+timeline slot. The rendering was fixed (86f1486, lanes now share width), but the
+underlying modelling is still odd — nothing about "trim hedges" happens at
+16:31:04. A date-only due date would be more honest.
diff --git a/project_doot_pull_punch_list.md b/project_doot_pull_punch_list.md
new file mode 100644
index 0000000..a9f479b
--- /dev/null
+++ b/project_doot_pull_punch_list.md
@@ -0,0 +1,36 @@
+---
+name: project-doot-pull-punch-list
+description: "doot's pull-based punch list (committed_date) shipped 2026-09-03; carry-over is the open design question — ask Peter about it on/after ~2026-09-10 if he hasn't raised it"
+metadata:
+ node_type: memory
+ type: project
+ originSessionId: dc4bccb0-7454-412c-9fcd-f9e336f62a74
+ modified: 2026-09-03T07:47:34.540Z
+---
+
+Reworked doot's task model so the day's list can actually reach zero. `due_date`
+had meant both "real deadline" and "when I plan to do it"; chains stamped
+`due_date = now` on every unlock, so milestones arrived looking overdue. Split
+into `due_date` (deadlines only) + `committed_date` ("I chose this for today").
+The day = committed work + genuine deadlines; chain steps and bucket items reach
+it only via a "+ today" pull. Shipped and verified against production 2026-09-03.
+Full design and remaining gaps live in the repo at
+`docs/superpowers/specs/2026-09-02-pull-based-punch-list-design.md` — read that
+rather than restating it here.
+
+**Open question — carry-over.** A task committed today and not finished simply
+stops appearing tomorrow; it does not show as carried. The choice (auto-roll
+forward vs. make him re-choose it during a review) is about how Peter wants to
+work, not something to infer.
+
+**Why:** He explicitly said he'd report back after living with it, and: "I'll let
+you know about the carryover thing (or if it's been a week or so feel free to
+ask)."
+
+**How to apply:** If a doot session happens on or after roughly **2026-09-10** and
+he hasn't raised carry-over, ask him directly — a short question about which
+behavior he wants, not a proposal to build both. Don't build carry-over before
+that answer. Related: [[feedback_investigate_the_actual_claim]] and
+[[feedback_user_does_not_read_code]] — this is exactly the class of question to
+ask about *intent* rather than infer from symptoms, which is what went wrong
+mid-session on the recurrence fix.
diff --git a/project_feedreader.md b/project_feedreader.md
index d884ee4..77baa8b 100644
--- a/project_feedreader.md
+++ b/project_feedreader.md
@@ -5,6 +5,7 @@ metadata:
node_type: memory
type: project
originSessionId: 375c858e-e817-41e8-a076-7920d22eab2b
+ modified: 2026-09-07T07:08:26.805Z
---
Building "FeeDreader" (renamed 2026-07-23 from "Feeds" — pun on "fee dreader", avoiding
@@ -22,4 +23,16 @@ tool available) and a short paywall-avoidance philosophy note in Settings.
**Why:** Inoreader (his prior RSS reader) removed OPML export, forcing a migration off it — his ~107 feeds were pulled via Inoreader's Reader API (ClientLogin) and re-packaged as OPML for import into this app.
-**How to apply:** Peter's workflow for this app is hands-on testing rounds — he sideloads a build to his phone, uses it for a while, then sends a batch of bug reports/feature requests in one message. Expect iterative APK rebuild-and-resend cycles, not a single big spec upfront. Room DB schema is at v6 as of 2026-07-23 (added `FeedEntity.folder`); see `AppDatabase.kt`'s migration comments — several past migrations needed a full table rebuild (Room validates exact column match, not just superset) rather than a plain ADD COLUMN. See [[feedback_feedreader_apk_naming]] and [[project_feedreader_emulator]].
+**Build trap (verified 2026-09-03): `/workspace/feedreader` has uncommitted
+dependency-upgrade work sitting in the working tree** — AGP 8.3.2→8.13.2,
+Kotlin 2.0.21→2.2.21, Gradle 8.6→9.5, compileSdk/minSdk/targetSdk 34→36, plus the
+whole androidx family bumped (the BACKLOG's "AGP / compileSdk upgrade" item,
+started but never finished or committed). Building without noticing produces a
+**66MB APK instead of ~18MB** — minSdk 36 flips AGP to storing `classes.dex`
+uncompressed — which exceeds the 30MB file-send limit. Before building a debug
+APK to send, check `git status`; if those five files are dirty, stash them
+(`git stash push -- app/build.gradle.kts build.gradle.kts gradle/wrapper/... gradlew`),
+build, then `git stash pop` to leave the tree as found. Don't commit or discard
+that work — it isn't finished and isn't mine.
+
+**How to apply:** Peter's workflow for this app is hands-on testing rounds — he sideloads a build to his phone, uses it for a while, then sends a batch of bug reports/feature requests in one message. Expect iterative APK rebuild-and-resend cycles, not a single big spec upfront. Room DB schema is at **v11** as of 2026-09-18 (was v6 on 2026-07-23 when `FeedEntity.folder` was added); see `AppDatabase.kt`'s migration comments — several past migrations needed a full table rebuild (Room validates exact column match, not just superset) rather than a plain ADD COLUMN. See [[feedback_feedreader_apk_naming]], [[project_feedreader_emulator]] and [[project_feedreader_apk_publishing]].
diff --git a/project_feedreader_apk_publishing.md b/project_feedreader_apk_publishing.md
new file mode 100644
index 0000000..a84b36f
--- /dev/null
+++ b/project_feedreader_apk_publishing.md
@@ -0,0 +1,47 @@
+---
+name: project-feedreader-apk-publishing
+description: "Peter installs FeeDreader from static.terst.org, not from files I send — publish there on every build, or he silently reinstalls a stale APK and hits a Room downgrade crash"
+metadata:
+ node_type: memory
+ type: project
+ originSessionId: 08c1f89c-4d24-4fb2-9488-0fe155872388
+ modified: 2026-09-18T15:40:16.440Z
+---
+
+**Peter gets FeeDreader builds from `https://static.terst.org/files/feedreader-debug.apk`**
+(on disk: `/site/static.terst.org/public/files/feedreader-debug.apk`), not from APKs sent in
+chat. He treats that URL as "the latest build".
+
+**Publish there whenever a new APK is produced.** Copy the debug APK over that path,
+`chmod 644`, `chown root:root`. Keep a dated `.bak` of the previous one before overwriting.
+Sending the file in chat as well is fine — see [[feedback_feedreader_apk_naming]] — but the
+URL is what he actually uses.
+
+**Why this matters (incident 2026-09-18):** the published APK had gone stale since 2026-07-24
+while the repo moved on. He downloaded it, installed over his existing data, and the app
+crashed on launch:
+
+```
+java.lang.IllegalStateException: A migration from 11 to 6 was required but not found
+ at androidx.room.RoomOpenHelper.onDowngrade
+```
+
+The old APK's `@Database(version = 6)` cannot open an on-disk database at version 11. Room
+has no downgrade path and `fallbackToDestructiveMigration` is deliberately NOT enabled in
+`AppDatabase.kt` ("since this app already has real data"), so it **throws rather than wiping** —
+user data survives intact, the app just refuses to start. Installing a current build over the
+top fixes it with no uninstall and no data loss; verified end to end on the API 36 emulator.
+
+**How to apply:** treat "crashes on launch after he installed a build" as a schema-downgrade
+suspect first, and check the published APK's date against the repo before anything else.
+Reproduce with: install current build → seed data → install the published APK over it →
+`adb shell am start -W -n org.terst.feedreader/.ui.MainActivity`.
+
+**Use `am start`, not `monkey`, to test launches on the emulator.** `monkey` gave false
+"crashed" and false "launched" readings repeatedly in this session — it reported the foreground
+activity as `com.android.settings/.spa.SpaActivity` and the app's pid came back empty even when
+the process was healthy. `am start -W` plus a continuously-running `adb logcat -v threadtime`
+capture (started *before* the launch) is the only combination that gave truthful results; the
+`-b crash` buffer was empty even for a genuine FATAL EXCEPTION.
+
+See [[project_feedreader]] and [[project_feedreader_emulator]].
diff --git a/project_feedreader_emulator.md b/project_feedreader_emulator.md
index 6b11e78..11a5c5f 100644
--- a/project_feedreader_emulator.md
+++ b/project_feedreader_emulator.md
@@ -1,13 +1,21 @@
---
name: project-feedreader-emulator
-description: "Android emulator set up on tungsten for direct testing of the feedreader (\"Feeds\") app instead of relying only on Peter's phone"
+description: "Android emulator on tungsten for direct testing of the feedreader app — use the API 36 AVD; the API 34 one was deleted 2026-09-18"
metadata:
node_type: memory
type: project
originSessionId: 375c858e-e817-41e8-a076-7920d22eab2b
---
-An Android emulator is provisioned on tungsten (the doot/feedreader host) for testing the "Feeds" app directly: AVD name `feedreader_test`, API 34, google_apis, x86_64, pixel_6 profile. Launch headless: `-no-window -no-audio -no-boot-anim -gpu swiftshader_indirect`. KVM-accelerated (host has `/dev/kvm` + AMD SVM). Controlled via `adb shell input tap/swipe`, `adb shell screencap`, and `adb shell uiautomator dump` for exact element bounds (more reliable than estimating tap coordinates from screenshots).
+An Android emulator is provisioned on tungsten (the doot/feedreader host) for testing the "Feeds" app directly.
+
+**Current AVD: `doot_test_api36` (API 36, google_apis, x86_64)** — use this one. The old
+`feedreader_test` AVD (API 34) and the entire `system-images/android-34` package were
+deleted 2026-09-18 at Peter's request, after the half-finished `minSdk 36` upgrade in the
+feedreader working tree made API 34 refuse the install and sent me down a wrong path. Do not
+recreate an API 34 AVD without asking. Note `platforms/android-34` is deliberately still
+installed — nav compiles against `compileSdk 34` and needs it; the *platform* and the
+*system image* are different things. Launch headless: `-no-window -no-audio -no-boot-anim -gpu swiftshader_indirect`. KVM-accelerated (host has `/dev/kvm` + AMD SVM). Controlled via `adb shell input tap/swipe`, `adb shell screencap`, and `adb shell uiautomator dump` for exact element bounds (more reliable than estimating tap coordinates from screenshots).
**Why:** Peter suggested this ("since you're on tungsten you should have enough resources to install an emulator") after a hard-to-reproduce "crash loop" bug report — sideload-to-phone-and-wait-for-feedback cycles were too slow for debugging. With the emulator, a real crash (WebView touched off the main thread during Readability-extraction cancellation) was reproduced directly via scripted rapid-swiping through articles, root-caused from an actual stack trace, and verified fixed under a heavier stress test — instead of guessing from code review alone.
@@ -24,3 +32,5 @@ succeed fully, or succeed with less content (some page sections render in via cl
some time after `onPageFinished`, racing against when our extraction script runs) across
back-to-back identical attempts with zero code changes in between. Retry 2-3 times (waiting
the full ~20-30s) before concluding a code change broke extraction.
+
+See [[project_feedreader_apk_publishing]] for the `am start` vs `monkey` trap when checking whether a launch actually succeeded.
diff --git a/project_feedreader_feature_candidates.md b/project_feedreader_feature_candidates.md
new file mode 100644
index 0000000..421e385
--- /dev/null
+++ b/project_feedreader_feature_candidates.md
@@ -0,0 +1,57 @@
+---
+name: project-feedreader-feature-candidates
+description: "Candidate FeeDreader features sourced from NetNewsWire's open feature requests + Brent Simmons' 'After Fixin' the Bugs' post (2026-08-26) — for future discussion, not yet committed to BACKLOG.md"
+metadata:
+ node_type: memory
+ type: project
+ originSessionId: dc4bccb0-7454-412c-9fcd-f9e336f62a74
+ modified: 2026-08-29T21:13:03.407Z
+---
+
+Peter asked me to research NetNewsWire's (https://github.com/Ranchero-Software/NetNewsWire)
+open feature-request issues plus Brent Simmons' blog post "After Fixin' the Bugs"
+(https://inessential.com/2026/08/26/after-fixin-the-bugs.html, NNW down to 10 open bugs,
+now facing 556 feature requests) for ideas relevant to [[project-feedreader]]. He ruled out
+sync, social features, and unread-count changes as not interesting. Confirmed two things
+against the actual FeeDreader code (as of 2026-08-27):
+
+- **Thumbnails/enclosures: already fully built**, matching Brent's own dependency chain
+ ("thumbnails depend on enclosure/Media RSS support"). `FeedParser.kt` parses
+ `media:thumbnail`, `media:content`, RSS `<enclosure type="image/*">`, Atom
+ `rel="enclosure"`, plus a first-`<img>` fallback → `ArticleEntity.imageUrl`, rendered as
+ both the list thumbnail and reader hero image. Worth testing against a Mastodon feed
+ specifically — Brent flags that as the case where the image lives only in the enclosure,
+ not the body.
+- **Pause/disable a feed: not built at all.** No `isPaused`/`isEnabled` field anywhere in
+ `Entities.kt`/`Daos.kt`. NNW itself has two separate open, undesigned requests for this:
+ #3401 "Disable feed" (hide but keep, 2022) and #4708 "Pause feed setting" (stop fetching
+ temporarily, Aug 2025) — neither has maintainer commentary yet. Cheapest shape for us: a
+ per-feed `isPaused` bool, skipped during refresh, row shown grayed out rather than hidden
+ (fits the existing "Last fetch failed" virtual-section pattern).
+
+**Candidate feature list (not yet in BACKLOG.md):**
+1. Regex filters — extend existing keyword mute/highlight to pattern matching. NNW's
+ oldest open feature request (#1864, since 2020).
+2. Pause a feed — see above.
+3. Limit posts/day from a noisy feed (NNW #4889) — inverse of the dead-feed-detection
+ backlog item: some feeds post too much instead of going quiet. Could reuse the same
+ per-feed cadence data.
+4. "Last updated" timestamp on the article list (NNW #4964) — feed-freshness signal,
+ complements the stale-feed badge already planned in BACKLOG.md.
+5. Timeline text-size setting (NNW #4925) — readability/accessibility.
+6. Copy link / share with the highlighted quote (NNW #4793) — extends the existing
+ share-sheet target.
+7. Per-feed JavaScript toggle (NNW #4788) — a scoped, explicit per-feed opt-in rather than
+ a global relaxation, so it doesn't undermine the app's JS-off-by-default security
+ posture ([[project_feedreader]] / CLAUDE.md quality gate item 5).
+8. Don't autoplay animations/video (NNW #4738) — general version of the tap-to-play
+ pattern already built for YouTube embeds (see BACKLOG.md "YouTube embeds (done)");
+ worth checking whether inline GIFs/`<video autoplay>` in regular articles still
+ autoplay through the WebView.
+
+**Why:** Peter wants a running list to reference next time he's thinking about new
+FeeDreader features, not an immediate build — this is explicitly deferred triage.
+
+**How to apply:** Bring these up whenever Peter is discussing new FeeDreader features or
+what to build next, rather than waiting for him to re-ask. Don't build any of these
+unprompted.