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
133 changes: 133 additions & 0 deletions docs/prompts/fix-merged-search-client-refilter.md
Original file line number Diff line number Diff line change
@@ -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.
99 changes: 99 additions & 0 deletions docs/prompts/fix-pair-error-copy.md
Original file line number Diff line number Diff line change
@@ -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 <changed files>` 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.
94 changes: 94 additions & 0 deletions docs/prompts/refresh-roadmap-status.md
Original file line number Diff line number Diff line change
@@ -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 <sha>:<path>` 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-<date> 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.
Loading