diff --git a/docs/prompts/fix-merged-search-client-refilter.md b/docs/prompts/fix-merged-search-client-refilter.md new file mode 100644 index 00000000..1c8cb6d8 --- /dev/null +++ b/docs/prompts/fix-merged-search-client-refilter.md @@ -0,0 +1,133 @@ +# Prompt — stop the hub re-filtering server search results + +Issue: **https://github.com/RonenMars/threadbase-mobile/issues/646** (remaining half) + +Hand this to a fresh agent session opened in `~/dev/ai-tools/tb-mobile`. + +> **Work in a new git worktree, outside the repo root.** Do not edit the main checkout — other sessions and verification runs use it, and a stray edit there gets read by whoever is mid-task. The exact command is under [Workflow](#workflow); the rules around it are not style preferences and are explained there. +> +> If `../tb-mobile-worktrees/search-refilter` or the branch `fix/merged-search-client-refilter` already exists, someone is running this now or has run it before — check `git worktree list` and `gh pr list` before starting, and pick a unique suffix. + +--- + +## Read this first + +The other half of #646 is already fixed in **https://github.com/RonenMars/threadbase-mobile/pull/657**. +That PR made `mergedClassicItems` take its conversations from `useConversationSearch` while a query is active, instead of from the paged set. + +Check whether #657 has merged before you start. +If it has not, base your work on it rather than on `main`, or you will be filtering a list that is still built from the wrong source and nothing you do will be observable. + +Do not redo #657's change. This prompt is only about what the client does to the list *after* it arrives. + +## The defect + +`/api/search` matches **message bodies**. The client then re-filters what it returns on **`title` and `preview` only**. + +So a conversation the server correctly matched — because the query appears deep in its message history — is fetched over the network and then silently discarded on the device, because neither its title nor its preview happens to contain the term. + +That is precisely the case server-side search exists to serve: *"I remember discussing wombat timeouts, I have no idea which conversation it was."* +The feature fails hardest exactly where it is most valuable, and it fails invisibly — the user sees an empty result list, not an error. + +## Verified state, 2026-08-12 + +Re-check these line numbers, the file moves often. + +`MergedClassicList` (`app/index.tsx:601` on `origin/main`) filters in `filteredItems` (`:640`): + +```js +const c = item.item as MultiConversation +return ( + c.title?.toLowerCase().includes(q) || + c.preview?.toLowerCase().includes(q) +) +``` + +That predicate is correct when the list is the locally-paged set. It is wrong when the list came from the server, where re-matching two fields can only ever remove correct results. + +The same memo also filters **session** rows, and those must keep filtering client-side — `/api/search` does not cover sessions. +The two cases have to be separated; that separation is the substance of this task. + +## What to build + +When the conversation list came from the server, do not filter it again — it is already filtered. +When it came from the paged set, filter it as today. +Sessions filter client-side in both cases. + +The suggested mechanism is to tell `MergedClassicList` which regime it is in — it already receives `searchQuery`, so a sibling boolean is the smallest change that expresses it. +If you find a cleaner approach, take it and explain the choice in the PR. + +What must be true at the end: **a conversation returned by `/api/search` is never dropped by the client**, and session rows still respond to the query. + +Resist a larger refactor of `filteredItems`. The defect is one predicate applied in one situation it does not fit. + +## Scope + +Files you own: + +- `app/index.tsx` +- tests under `__tests__/` +- `e2e/fixtures/search-results.json` — only if you extend it, see below + +Do not touch `e2e/*.yaml`, `docs/`, `.github/`, or `package.json`. + +## The trap: a green e2e suite proves nothing here + +`e2e/06_search_anchor.yaml` passes **with this bug present**. + +`e2e/fixtures/search-results.json` gives the anchor a `preview` of "Where did we set the wombat timeout for the retry loop?", which contains the query `wombat`, so the client-side predicate keeps it either way. + +Do not use that flow as evidence. It cannot distinguish fixed from broken. + +If you extend the fixture with a conversation whose match is body-only, say so explicitly in the PR — that file is shared with the e2e flow, and changing it changes what that flow exercises. + +## Tests + +Write a unit test that **fails without your fix**. Prove it fails by reverting your change, running it, and restoring — do not assume. + +The essential case: a conversation present in the server results whose query term appears in **neither** `title` nor `preview` still reaches the rendered list. + +Also cover, because they are easy to break while fixing the above: + +- With no active query, the paged list still filters normally. +- Session rows still filter by the query while conversations are server-backed. + +## Do not use the iOS simulator + +It may be in use for verification runs. Implement and verify with jest, eslint and tsc only — no Maestro, no `npm run ios`, no `xcodebuild`. +List the on-device check steps in your PR for whoever runs it next. + +## Workflow + +```bash +/opt/homebrew/bin/git -C /Users/ronenmars/dev/ai-tools/tb-mobile fetch origin +/opt/homebrew/bin/git -C /Users/ronenmars/dev/ai-tools/tb-mobile worktree add \ + ../tb-mobile-worktrees/search-refilter -b fix/merged-search-client-refilter origin/main +cd /Users/ronenmars/dev/ai-tools/tb-mobile-worktrees/search-refilter +cp -Rc /Users/ronenmars/dev/ai-tools/tb-mobile/node_modules ./node_modules +``` + +If #657 has not merged, branch from its head instead of `origin/main`. + +Repo rules that are not style preferences: + +- Worktrees live **outside** the repo root. A nested one gets discovered by jest, eslint and Metro and produces failures from a stale branch. +- `node_modules` must be a **real copy**, never a symlink, or Metro silently bundles the main checkout and you test code you did not write. +- jest needs `--watchman=false` in a fresh worktree or it hangs with no output. +- Use the absolute git binary `/opt/homebrew/bin/git`; a shell function shadows `git` on this machine. + +## Verify before claiming done + +`npx tsc --noEmit --pretty false` — the baseline is **14** pre-existing `TS2345` Expo Router typed-route errors, tracked as https://github.com/RonenMars/threadbase-mobile/issues/606. Adding any is a regression. + +**`.expo/types/router.d.ts` does not exist in a fresh worktree**, and without it tsc reports 0 — a false clean that would hide a real regression. Generate it first (briefly run `npx expo start`, then kill it) before trusting the count. + +Then `npx eslint app/index.tsx` and `npx jest --ci --watchman=false --runInBand --testPathPattern "index|search"`. + +## Deliverable + +Conventional commit title (`fix(hub): …`), one sentence per line in the body, **no AI attribution anywhere**. + +Push and open a PR against `main` with `gh pr create`, linking issue #646 and noting that it completes the work started in #657. + +Report: the mechanism you chose for distinguishing server-backed from paged results, the test output including the fails-without-the-fix check, whether you extended the shared fixture, and the PR URL. diff --git a/docs/prompts/fix-pair-error-copy.md b/docs/prompts/fix-pair-error-copy.md new file mode 100644 index 00000000..8a30abd1 --- /dev/null +++ b/docs/prompts/fix-pair-error-copy.md @@ -0,0 +1,99 @@ +# Prompt — fix the raw exception on the pair deep-link error screen + +Issue: **https://github.com/RonenMars/threadbase-mobile/issues/638** + +Hand this to a fresh agent session opened in `~/dev/ai-tools/tb-mobile`. + +> **Work in a new git worktree, outside the repo root.** Do not edit the main checkout — other sessions and verification runs use it, and a stray edit there gets read by whoever is mid-task. The exact command is under [Workflow](#workflow); the rules around it are not style preferences and are explained there. +> +> If `../tb-mobile-worktrees/pair-error-copy` or the branch `fix/pair-error-copy` already exists, someone is running this now or has run it before — check `git worktree list` and `gh pr list` before starting, and pick a unique suffix rather than reusing them. + +--- + +## The defect + +The `threadbase://pair` deep-link error screen shows a raw exception string instead of translated copy. + +Observed on device 2026-08-11 (iPhone 17 Pro Max / iOS 26.1, Release build of `97f2869c`), fresh install with zero paired servers, opening a pair link whose host was unreachable: + +> **Pairing failed** +> Could not reach the server: fetch failed: UnexpectedException: Could not connect to the server. (at ExpoModulesCore/Promise.swift:56) + +Three problems in one string. It is not localized — the surrounding "Pairing failed" / "Try again" / "Contact support" chrome **is** translated, so the mismatch is visible mid-screen. It leaks an internal Swift path. And it tells the user nothing actionable. + +This is the first screen a new user sees on the flow onboarding instructs them to use, so it is the worst placement in the product for developer-facing text. + +## Verified state + +`app/pair.tsx` was built to reuse the QR scanner's translated taxonomy under the `pair:scanner.errors.*` keys, and that works for the failure shapes it recognises. This path is different: the raw `Error.message` from the failed `exchangeToken` fetch is surfaced instead of being mapped onto one of those keys. + +**The bug is in the mapping, not the copy** — an unrecognised failure falls through to the exception text rather than to a translated fallback. Confirm that reading yourself before changing anything. + +## Scope + +Files you own: + +- `app/pair.tsx` +- `locales/{en,he,ru,ar}/pair.json` +- tests under `__tests__/` + +Do not touch `docs/`, `app/_layout.tsx`, `e2e/`, `.github/`, or `package.json`. + +## What "fixed" looks like + +An unreachable or refused host produces a translated, actionable message in all four locales — in the spirit of *"Could not reach that server. Check the streamer is running and that your phone is on the same network."* The wording is yours, but it must name a next action, not just a cause. + +No internal file path, no `UnexpectedException`, no `Promise.swift` reaches the screen in any locale. + +Keep the raw string diagnosable — route it to `lib/clientLog.ts` or the existing diagnostics surface. Do not discard it; the next person debugging a pairing failure needs it. + +**Make the fallback total.** Any unrecognised error must land on a translated generic message rather than on `Error.message`. That is the actual defect — a narrow fix that only special-cases "connection refused" leaves the same hole open for the next unmapped shape. + +## Locales + +All four (`en`, `he`, `ru`, `ar`) or the i18n CI job fails on missing keys. `he` and `ar` are RTL. + +Prefer reusing an existing translated `pair:scanner.errors.*` key where one genuinely fits — that was the original design and those strings are already vetted. Add a new key only when nothing fits. + +If you add a key and cannot write a locale confidently, **say so explicitly in the PR** rather than guessing. Wrong copy in a language nobody on the team reads is worse than a flagged gap. Do not machine-translate silently. + +Related open follow-up, **not your scope**: the reused scanner strings say "QR" where this entry point is a tapped link. Do not rewrite the scanner copy — just do not make it worse. + +## Do not use the iOS simulator + +It may be in use for verification runs. Implement and verify with jest, eslint and tsc only — no Maestro, no `npm run ios`, no `xcodebuild`, no install to a simulator. List the on-device check steps in your PR for whoever runs it next. + +## Workflow + +```bash +/opt/homebrew/bin/git -C /Users/ronenmars/dev/ai-tools/tb-mobile fetch origin +/opt/homebrew/bin/git -C /Users/ronenmars/dev/ai-tools/tb-mobile worktree add \ + ../tb-mobile-worktrees/pair-error-copy -b fix/pair-error-copy origin/main +cd /Users/ronenmars/dev/ai-tools/tb-mobile-worktrees/pair-error-copy +cp -Rc /Users/ronenmars/dev/ai-tools/tb-mobile/node_modules ./node_modules +``` + +Repo rules that are not style preferences: + +- Worktrees live **outside** the repo root. A nested one gets discovered by jest, eslint and Metro and produces failures from a stale branch. +- `node_modules` must be a **real copy**, never a symlink, or Metro silently bundles the main checkout and you test code you did not write. +- jest needs `--watchman=false` in a fresh worktree or it hangs with no output. +- Use the absolute git binary `/opt/homebrew/bin/git`; a shell function shadows `git` on this machine. + +## Verify before claiming done + +`npx tsc --noEmit --pretty false` — the baseline is **14** pre-existing `TS2345` Expo Router typed-route errors, tracked as https://github.com/RonenMars/threadbase-mobile/issues/606. Adding any is a regression. + +**`.expo/types/router.d.ts` does not exist in a fresh worktree**, and without it tsc reports 0 — a false clean that would hide a real regression. Generate it first (briefly run `npx expo start`, then kill it) before trusting the count. + +Then `npx eslint ` and `npx jest --ci --watchman=false --runInBand --testPathPattern "pair|i18n"`. + +Write a test that **fails without your fix**: assert an unmapped error produces the translated fallback rather than `Error.message`. Prove it fails by reverting your change, running it, and restoring — do not assume. + +## Deliverable + +Conventional commit title (`fix(onboarding): …`), one sentence per line in the body, **no AI attribution anywhere** — no `Co-Authored-By`, no "Generated with", no robot emoji. + +Push and open a PR against `main` with `gh pr create`, linking issue #638. + +Report: the mapping approach, which locales you wrote confidently and which you flagged, the test output including the fails-without-the-fix check, and the PR URL. diff --git a/docs/prompts/refresh-roadmap-status.md b/docs/prompts/refresh-roadmap-status.md new file mode 100644 index 00000000..83eac612 --- /dev/null +++ b/docs/prompts/refresh-roadmap-status.md @@ -0,0 +1,94 @@ +# Prompt — refresh the roadmap and backlog status against reality + +Hand this to a fresh agent session opened in `~/dev/ai-tools/tb-mobile`. + +Re-runnable: the dated facts below go stale quickly, so treat every one of them as a *starting point, not evidence*, and re-verify before writing. + +> **Work in a new git worktree, outside the repo root.** Do not edit the main checkout — other sessions and verification runs use it, and a stray edit there gets read by whoever is mid-task. The exact command is under [Workflow](#workflow); the rules around it are not style preferences and are explained there. +> +> Because this prompt is meant to be re-run, **give the branch and worktree a unique suffix each time** — `docs/roadmap-refresh-2026-08-11`, then `-2026-09-02`, and so on. Check `git worktree list` and `gh pr list` first; if a previous refresh is still open, rebase onto it or wait rather than opening a competing PR against the same files. + +--- + +## Why this is needed + +`docs/ROADMAP.md` and `docs/BACKLOG.md` were swept on 2026-08-10 and drifted again within a day. + +This repo has been bitten repeatedly by status tables that record **intent rather than outcome** — an audit once found trackers listing eight already-merged PRs as open work. So the standing rule: + +**Verify every claim at its source.** `gh issue view` / `gh pr view` for state, `grep -n` at a `path:line` for code, `git show :` for committed content. Never restate what another doc says — that is the mechanism by which these files went wrong in the first place. + +A related trap worth knowing: **a CLOSED PR is not an unlanded change.** PRs #343, #345 and #346 are all closed rather than merged, yet all three fixes are in the tree — they landed by another route. Check the code, not the PR state. + +## What changed since the last sweep (verify each) + +**Merged to `main`:** + +- PR #626 → `2de1c1ae` — `e2e/ensure-release-build.js` fails fast on a stale build instead of silently reusing it. Closed issue #598. +- PR #627 → `24627baa` — the `threadbase://pair` deep-link route, plus an AuthGate fix so a cold start with zero servers is not bounced to onboarding. Closed issue #597. +- PR #625 → `4eb1ae54` — scheduled E2E failure alert (`notify-schedule-failure` in `.github/workflows/e2e.yml`), gated on a `CI_ALERT_WEBHOOK` secret. Closed issue #599. +- PR #632 → `e935a6c8` — corrected alert payload compatibility docs; Telegram chosen as the channel. +- PR #634 → `b512d83d` — a `workflow_dispatch` smoke test for the alert secret. + +Issues #597 and #598 were closed after **real-device verification** on 2026-08-11, not merely CI. + +**Open issues:** + +- https://github.com/RonenMars/threadbase-mobile/issues/636 (P0) — the Live Activity push payload carries terminal output, a prompt-derived title and the project name, while the privacy policy claims payloads exclude exactly those. The policy also describes an Expo relay that does not exist. +- https://github.com/RonenMars/threadbase-mobile/issues/638 (P2) — the pair deep-link error screen shows a raw exception instead of translated copy. May be fixed by the time you run this; check. +- https://github.com/RonenMars/threadbase-streamer/issues/528 (P1) — the streamer has **no ordinary push sender at all**, so a self-hosted streamer can deliver no notifications. + +## The substantive finding, which is not a status flip + +**Live Activities cannot work for self-hosters, ever.** + +The streamer signs APNs pushes itself and targets `${bundleId}.push-type.liveactivity`. Apple issues APNs keys per developer team, and a key only signs topics for bundle IDs that team owns — so a self-hosted streamer cannot sign for the published app's bundle ID. That is Apple's trust boundary, not a configuration gap. + +**Ordinary notifications are not implemented at all** — no Expo client, no FCM, no sender of any kind in the streamer. `POST /api/push/register` stores tokens that nothing consumes except the Live Activity sender. + +Check how `docs/ROADMAP.md` describes Feature 12 (Live Activities / Dynamic Island) and anything mentioning notifications, and correct it. A roadmap entry implying every user gets Live Activities is wrong in a way that matters — the landing site is being corrected for the same overclaim. + +## Scope + +Files you own: + +- `docs/ROADMAP.md` +- `docs/BACKLOG.md` +- `docs/followups/RELEASE-READINESS-2026-08-10.md` — its P0 table lists #597/#598/#599 as open. Add a dated update rather than rewriting history, matching how that file already handles supersession. + +Do not touch anything outside `docs/`. + +## How to write it + +Match the existing conventions: status markers (`✅ DONE`, `🟡 Partial`, `⛔ Obsolete`), evidence inline (`path/to/file.ts:123`, a PR number, a commit SHA), and **full GitHub URLs for issues and PRs, not bare `#numbers`** — work spans four repos and a bare number is ambiguous about which. + +One sentence per line. No AI attribution anywhere. + +Where an item is genuinely uncertain, say so and say what would settle it. Do not manufacture a status you have not verified. + +## Workflow + +```bash +/opt/homebrew/bin/git -C /Users/ronenmars/dev/ai-tools/tb-mobile fetch origin +/opt/homebrew/bin/git -C /Users/ronenmars/dev/ai-tools/tb-mobile worktree add \ + ../tb-mobile-worktrees/roadmap-refresh -b docs/roadmap-refresh- origin/main +cd /Users/ronenmars/dev/ai-tools/tb-mobile-worktrees/roadmap-refresh +``` + +Worktrees live **outside** the repo root — never nest one under the checkout, because jest, eslint and Metro all walk into a nested copy and report failures from a stale branch. You do not need `node_modules` for a docs-only change. Use the absolute git binary `/opt/homebrew/bin/git`; a shell function shadows `git` on this machine. + +Docs-only, so the `commit-msg` hook appends `[skip-ci]`. That is correct, not a failure. + +Do not use the iOS simulator — it may be in use for verification runs. + +## Verify before claiming done + +- Every relative link in the files you touched resolves. +- Every issue/PR state you assert matches `gh issue view` / `gh pr view` **at the time you write it**. +- No claim rests solely on what another doc says. + +## Deliverable + +Conventional commit title (`docs: …`), one sentence per line, no AI attribution. Push and open a PR against `main`. + +Report: what you changed, which claims you re-verified and which you found already stale, and the PR URL.