From 183d0d5356fad989731c99adeca8f0b427d73d43 Mon Sep 17 00:00:00 2001 From: spacedandy-agents-simplex <342056592+spacedandy-agents-simplex@users.noreply.github.com> Date: Fri, 16 Oct 2026 21:01:26 +0000 Subject: [PATCH] ci: restore the iOS device test plan for online search, with its prompt and scope The Claude sessions run the online search plan again: the cases, what each session covers per layout, the prompt's directory search, names and report sections, and the session's start state after the smoke test's searches. --- scripts/ci/ios-sim/claude-prompt.md | 44 +++++++---- scripts/ci/ios-sim/claude-scope | 11 ++- scripts/ci/ios-sim/claude-test.sh | 6 +- scripts/ci/ios-sim/device-test-plan.md | 103 +++++++++++++++++++++++-- 4 files changed, 137 insertions(+), 27 deletions(-) diff --git a/scripts/ci/ios-sim/claude-prompt.md b/scripts/ci/ios-sim/claude-prompt.md index 84c3903f82..8bcc7c4be7 100644 --- a/scripts/ci/ios-sim/claude-prompt.md +++ b/scripts/ci/ios-sim/claude-prompt.md @@ -1,6 +1,6 @@ -# Device test session: SimpleX Chat iOS +# Device test session: SimpleX Chat iOS, online search -You are testing the SimpleX Chat iOS app in an iPhone 17 simulator (iOS 26.5, screen 402 x 874 points) on a GitHub Actions runner. You run the cases of the device test plan (at the end of this prompt) that the section "This session" names, in the layout it names, and write a report. You cannot change the app or the runner; anything wrong you find is a finding for the report. +You are testing the SimpleX Chat iOS app in an iPhone 17 simulator (iOS 26.5, screen 402 x 874 points) on a GitHub Actions runner. The feature under test is online search: from the chat list and from New chat, the app sends the search text to a SimpleX Directory and shows its listed groups and channels. You run the cases of the device test plan (at the end of this prompt) that the section "This session" names, in the layout it names, and write a report. You cannot change the app or the runner; anything wrong you find is a finding for the report. ## Tools @@ -9,40 +9,51 @@ You are testing the SimpleX Chat iOS app in an iPhone 17 simulator (iOS 26.5, sc ## What you need to know about the app and the helper -- **Layouts.** With the one-hand UI (reachable toolbar, the default) the chat list is drawn from the bottom up: the toolbar (your profile image and the "Compose" pencil) is at the bottom, the search field just above it (y about 754), the list tags ("Add list" when there are no lists) above the field, and chats stack upwards from there. With the top bar the toolbar and the search field are at the top. A profile without conversations sees onboarding pages ("Talk to someone") in place of the list, without the search field. Stay in the layout the session names; the app starts in it, and `ui relaunch` keeps it. +- **Layouts.** With the one-hand UI (reachable toolbar, the default) the chat list is drawn from the bottom up: the toolbar (your profile image and the "Compose" pencil) is at the bottom, the search field just above it (y about 754), the list tags ("Add list" when there are no lists) or the online search rows above the field, and chats and results stack upwards from there. With the top bar the toolbar and the search field are at the top, the tags above the field, and "Search in Directory" and "Connect to …" appear below it. A profile without conversations sees onboarding pages ("Talk to someone") in place of the list, with the search field above them. Stay in the layout the session names; the app starts in it, and `ui relaunch` keeps it. - **Positions.** "Above" means a smaller y. Judge order and position from the tree's coordinates, and look at a screenshot when you judge layout (truncation, overlap, images). - **The search field** has no label; tap it with `ui tap-value "Search or paste SimpleX link"` when empty, or by its text. To clear it, tap the field so it is focused, then "Cancel" next to it ("Cancel" exists only while the field is focused; it also hides the keyboard); tapping the "Close" image inside the field did not clear it in an earlier run. Type text as one quoted argument (`ui type "ci seed"`). The keyboard capitalizes the first letter and autocorrects words (an earlier run saw "ci" become "Ci" and a typed name become another word when a button was tapped), so check the field's value in the tree after typing, and before typing check that the field shows its placeholder. The keyboard's Search key is `ui key return`; the keyboard's keys are below the screen and not listed in the tree, and the screenshots do not show the keyboard. -- **Pasting**: `ui clipboard ""`, tap the empty field once to focus it and once more to open its menu, then tap its "Paste" item (two elements carry that label: use `ui tap-xy` on the one `ui find Paste` shows in the menu). +- **Pasting** (C4, W3): `ui clipboard ""` with the link given under "This session", tap the empty field once to focus it and once more to open its menu, then tap its "Paste" item (two elements carry that label: use `ui tap-xy` on the one `ui find Paste` shows in the menu). +- **Directory search.** "Search in Directory" (a row) searches the directory; the first time after the hints are reset the alert "Search in Directory?" asks first, with "Cancel", "Search" and "Search and don't show again" (`ui tap Search` taps the alert's button). Results come as a heading `Heading "Directory"`, then per entry an avatar image, `"#name"`, the description, "N members" or "N subscriber(s)", and "Join group" or "Join channel" as separate texts. A failed search shows "Search failed, tap to retry"; more results show "Show more". A search takes 1–2 s here (it sets up an encrypted connection) and times out after 10 s. A channel's row shows the subscriber count the directory publishes for it (`publicMemberCount`), not the listings' members column, which counts every member record (owner, relay, directory), so the two differ by design. +- **Order with the one-hand UI.** Results read from the bottom like the chats: the first result is nearest the search field, each "Show more" adds its page above the earlier ones, and "Show more" sits right under the "Directory" heading. That is the current design; what must hold is that the heading is above all of the part's rows. - **Disabled elements** end with "disabled" in the tree. +- **Holding a search open.** A search answers in 1–2 s here, too fast to see its loading state. For the cases about a search in progress (D1, D12, D13, W9, G3, and "Show more" while loading in D7), stop the directory first (`ui directory stop`): the search then waits 10 s before it fails. Start it again afterwards. - **Ambiguous labels.** When `ui tap` says several elements match a label (seen for "Paste", "Channels", "Account"), tap the one you mean with `ui tap-xy` at its centre, taken from `ui find`. - **Hidden elements.** The tree always contains the closed user picker: the profile name, "Create SimpleX address", "Chat preferences", "Your chat profiles", "Use from desktop", "Settings", at coordinates on the screen even while it is hidden. Do not tap them unless you opened the picker by tapping your profile image in the toolbar (`Image "Account"`, 32 x 32; tap its centre with `ui tap-xy`). - **Settings → Reset all hints:** open the picker, "Settings", scroll to the bottom, the app version row ("v…"), "Developer", "Reset all hints". Go back with the navigation back buttons and close Settings by swiping it down. -- **New chat** is the "Compose" button (the pencil) in the toolbar. With the top bar the toolbar is hidden while the search field is focused. +- **New chat** is the "Compose" button (the pencil) in the toolbar. With the top bar the toolbar is hidden while the search field is focused; tapping the "Search in Directory" row hides the keyboard, and the pencil shows again while that search runs. +- **Counting requests.** `ui requests` tells how many directory searches the app sent; compare before and after an action to prove that something was or was not sent (C6, C9, D12, D13). - **Joining** (the app's normal flow, from `NewChatView.swift`; an earlier session took its first step for a failure). Tapping a group or channel the profile does not know yet, from a link or a list, shows an alert with its name (and subscriber count for a channel) and "Cancel" / "Open channel" or "Open group": that is the start of joining, not a sign that the entry is known. "Open …" opens the chat, not yet joined, with "Tap Join channel" in it and a "Join channel" / "Join group" bar at the bottom; tapping that bar joins (a channel then shows "you are subscriber"; a group may stay "connecting"). A contact works the same way: "Open new chat", then the chat's connect button. An entry already opened, joined or connecting shows the same kind of alert, plus your role ("You are a subscriber", "You are a member") once joined. So each case that joins uses the entry named for it under "This session", which no earlier case has touched. Opened and joined chats appear in the chat list, and the profile is no longer "without conversations". -- **A second profile:** open the picker, "Your chat profiles", add a profile; a new profile has no conversations. Switch back to the first profile when done. -- **Text size:** `ui text-size accessibility-large`, and back to `ui text-size medium` afterwards. Only one phone size is available. -- **If the app gets stuck**, `ui relaunch` restarts it (search text is cleared, everything else stays). +- **A second profile** (D14, and D15 once the first profile has chats): open the picker, "Your chat profiles", add a profile; a new profile has no conversations. Switch back to the first profile when done. +- **Text size** (D3): `ui text-size accessibility-large`, and back to `ui text-size medium` afterwards. Only one phone size is available. +- **If the app gets stuck**, `ui relaunch` restarts it (search text and results are cleared, everything else stays). - Waiting: prefer `ui wait ` over repeated trees; `ui wait` returns at once when the text is already shown. ## The test backend -When "This session" gives the test directory's address, the runner also runs a test backend: a chat relay, a SimpleX Directory with the groups and channels listed under "Test directory" below, a server that resolves SimpleX names, and contacts that accept connections. `ui directory stop|start` stops and starts the directory (after `ui directory start`, wait about 15 s before using it), and `ui listings` prints what it lists. +When "This session" gives the test directory's address, the runner also runs a test backend: a chat relay, a SimpleX Directory with the groups and channels listed under "Test directory" below, a server that resolves SimpleX names, and contacts that accept connections. `ui directory stop|start` stops and starts the directory (after `ui directory start`, wait about 15 s before using it), and `ui listings` prints what it lists. Stop the directory instead of turning networking off (D8, D9; the simulator shares the runner's network): while it is stopped, searches fail after 10 s. **SimpleX names**: the names server's address is under "This session"; the names it knows are under "Test directory" → "SimpleX names" (all end in `.testing`; a bare word of 5 or more letters is completed with `.testing`, so `cinews` is `cinews.testing`). Set the app up for it once, before the first case that needs names (if an earlier session in this run did it, check it instead: the server is listed under "Your servers"): 1. Open the picker (your profile image), "Settings", "Network & servers". 2. Under "Preset servers", open each operator ("SimpleX Chat", "Flux"), turn "To resolve names" off, and go back. Otherwise the app may ask the real servers, which do not know the test names. -3. "Your servers" → "Add server" → "Enter server manually". Put the names server's address in the address field by pasting it (typing it would let the keyboard capitalize it), check it in the tree, and go back. +3. "Your servers" → "Add server" → "Enter server manually". Put the names server's address in the address field by pasting it (`ui clipboard "
"`, tap the field twice, tap the menu's "Paste"; typing it would let the keyboard capitalize it), check it in the tree, and go back. 4. In "Your servers", open the new server and set "To receive" to "no", "For private routing" to "no" and "To resolve names" to "yes" (each is a picker), then go back. 5. On "Network & servers", tap "Save servers" (and "Save" if asked). Close Settings. +6. Check: type `cinews`, press the Search key: a "Names" part with a row for the channel cinews must appear. If it does not, record what you see and look at the steps again before running the N cases. + +**What the names are for:** `cinews` is a listed channel (N1, N6, the full search); `cialice` and `cibobby` are contacts that accept a connection at once (N2, N5, N10, W7, W8, G1; `cibobby`'s profile is called "Robert", so its name is not in its display name); `ciboth` has both a contact address and a channel (N3); `cinone` is not registered (N4). N7 needs the second profile with "To resolve names" off for both operators (step 2 only, no server added). Which case uses which contact in this layout is under "This session". + +**The full search** (after N1): with a local chat whose name contains "cinews" (join the group cinews_digest from its Directory row first, if it is not in your chats yet), type `cinews` and press the Search key. The plan's parts are the chats already loaded, a "Names" part and a "Directory" part (N1: the "Names" header above the Directory part; W4 for New chat: "Names" and "Directory" after "Your Contacts"). Record for this layout, from the tree, every header and row with its y, top to bottom: the local chats, "Names" with its rows, "Directory" with its rows (cinews_weekly; cinews should not repeat there, as it is the Names row), and say whether the part names and their order match the plan. Do the same in New chat (W4) when the session's cases include New chat. ## How to run the cases -- Run each case of the session's list, in the plan's order unless a case needs another first. +- Run each case of the session's list, in the plan's order unless a case needs another first (for example D15 needs a profile without conversations, U1 and U2 need a joined channel from D6). - The plan's tables (ID, Do, Expect) are at the end of `prompt.md` in the current directory: read the row before running a case and do exactly what it says. An earlier session did not find them and filed other actions under some IDs. -- For each case, do what the plan says, decide pass or fail against the plan's "Expect" column, and take at least one `ui shot` at the moment that shows the result, named after the case and layout without a number, for example `ui shot S1-reachable` (the helper numbers the files). +- For each case, do what the plan says, decide pass or fail against the plan's "Expect" column, and take at least one `ui shot` at the moment that shows the result, named after the case and layout without a number, for example `ui shot D2-reachable` (the helper numbers the files). - Pass only what you saw: quote the evidence (element labels, values and coordinates from the tree, or what a screenshot shows). If an expectation cannot be checked here, say which part and why, and mark the case passed for the parts you checked only if those parts are the substance of the case; otherwise skipped. -- **Skip** any case that needs something this runner lacks, saying exactly what is missing. Do not skip a case only because it takes several steps. +- **Skip** any case that needs something this runner lacks, saying exactly what is missing. Known: U3 (needs a listed group joined through another link), W10 (needs a notification from another profile), W11 unless "Archived contacts" is shown. Do not skip a case only because it takes several steps. +- **The Directory header** must be above its results in this layout (D2, D4, D8 and every screen with results): record the heading's y and the first and last result's y each time, and note whether `Heading "Directory"` comes before or after the result texts in the tree's order. +- **Avatars and the response size limit** (part of D2 and D7): search `ci avatar`. Six entries match; each has an avatar about the size the app makes, and a directory response must fit 10737 bytes, so expect about one entry per page. Tap "Show more" until it disappears and record which entries each page added. Compare with the page-by-page table under "Test directory" below: every entry reachable, none twice, none silently missing. Look at the screenshots: does every row show its picture (not the placeholder icon), is any row drawn wrong? - Other app bugs you notice are findings: write what you did, what you saw, what you expected, and the screenshot. - Mind the session's turn limit under "This session": every `ui` command and every file edit is a turn. @@ -52,8 +63,9 @@ Write `report.md` in the current directory as you go, so a session that is cut s 1. `# Device test report: ` and one line on the app state at the start. 2. `## Results`: a table with one row per case of the session's list: `| ID | Result | Evidence | Screenshots |`, Result being pass, fail or skipped (with the reason). -3. Any section the plan asks for. -4. `## Findings`: app bugs, each with steps, what you saw, what you expected, screenshots. -5. `## Harness notes`: anything about `ui` or the runner that got in the way. +3. `## Directory header`: per screen with results, the heading's y and the first and last result's y, and the heading's place in the tree order. +4. `## Avatars and the size limit` (when the session's cases include D2 or D7): what each "Show more" added, compared with the table below, and how the avatars looked. +5. `## Findings`: app bugs, each with steps, what you saw, what you expected, screenshots. +6. `## Harness notes`: anything about `ui` or the runner that got in the way. End the session by making sure `report.md` is complete and no row says `todo`; your final message is a three-line summary: passed, failed and skipped counts, and the most important finding. diff --git a/scripts/ci/ios-sim/claude-scope b/scripts/ci/ios-sim/claude-scope index a846ae4ac5..5e73b14d46 100644 --- a/scripts/ci/ios-sim/claude-scope +++ b/scripts/ci/ios-sim/claude-scope @@ -2,7 +2,10 @@ # A case runs in both layouts only when the layouts render it differently; the rest run in one. # entries- (optional) tells that session which directory entries and names its cases use, so none meets an entry already known. model: claude-haiku-5-5 -layouts: reachable -cases-reachable: S1-S3 -max-turns: 60 -max-budget-usd: 3 +layouts: reachable top +cases-reachable: the SimpleX names setup, then N1-N11, W4 with names, W7, W8, G1 and the full search (chat list and New chat) +cases-top: N1 and N4 (this layout places the "Connect to …" row and the Names and Directory parts differently) and the full search on the chat list only (New chat looks the same in both layouts); the names setup is already done +entries-reachable: N7 uses a second profile you add (the first profile has no other); N10 connects to @cialice.testing, which N5, W7 and G1 then use; W8 first connects to @cibobby.testing (Robert) from its Names row; the full search joins the group cinews_digest +entries-top: the earlier session joined cinews_digest and connected to cialice and Robert; N1 and N4 join nothing +max-turns: 900 +max-budget-usd: 5 diff --git a/scripts/ci/ios-sim/claude-test.sh b/scripts/ci/ios-sim/claude-test.sh index 5d72d03a9b..870aef196b 100755 --- a/scripts/ci/ios-sim/claude-test.sh +++ b/scripts/ci/ios-sim/claude-test.sh @@ -67,12 +67,14 @@ write_prompt() { echo "- Entries to use: $entries." fi if [ -n "$BACKEND_DIR" ]; then - echo "- The test directory's address: $(cat "$BACKEND_DIR/directory-link")." + echo "- Link to paste (C4, W3): $(cat "$BACKEND_DIR/directory-link") (the test directory's address)." echo "- Names server to add in the app: $(cat "$BACKEND_DIR/names-server")." else echo "- No test backend runs in this session." fi - if [ -z "$previous" ]; then + if [ -z "$previous" ] && [ -n "$BACKEND_DIR" ]; then + echo "- At the start: the smoke test onboarded a profile (typed as \"citester\"; the keyboard may have autocorrected the name), searched the directory in both layouts, choosing \"Search\" in the alert each time, and joined the channel ci_cards, which no case uses; it is the profile's only conversation." + elif [ -z "$previous" ]; then echo "- At the start: the smoke test onboarded a profile (typed as \"citester\"; the keyboard may have autocorrected the name) and checked the chat list in both layouts; the profile has no conversations." else echo "- At the start: an earlier session ran the cases in $(layout_name "$previous") on this app, so the profile may have chats, other profiles may exist and hints may be changed; look at the state first." diff --git a/scripts/ci/ios-sim/device-test-plan.md b/scripts/ci/ios-sim/device-test-plan.md index cf82b7daf0..07ef392e97 100644 --- a/scripts/ci/ios-sim/device-test-plan.md +++ b/scripts/ci/ios-sim/device-test-plan.md @@ -1,9 +1,102 @@ -# Device test plan: smoke test +# Device test plan: iOS online search (f/directory-search) -The app as the scripted smoke test leaves it: a profile created through onboarding, without conversations. A branch that tests a feature replaces this plan with its own (same table format) and names its cases in `claude-scope`. +Run these on an iOS simulator or device on the Mac. Each case says what to do and what you should see. The case IDs map to the run's acceptance criteria (AC1–AC8) and review fixes (R1-x). + +## Setup + +1. **Build** `f/directory-search` (HEAD `e7678b634` or later) in Xcode on a simulator. Use a small one (iPhone SE) and a large one, or switch Dynamic Type to a large size for the layout cases. +2. **A directory the app can reach with service requests.** The app sends every search to `DIRECTORY_SERVICE_LINK` (`apps/ios/Shared/Model/DirectorySearch.swift:14`), which is the live SimpleX Directory. Searches against the live directory fail with `ASENotDRAddress` until it runs `/_rotate_address_keys `. Pick one: + - **Live directory:** rotate its address keys first. The published link does not change. + - **Test directory:** run `simplex-directory-service` against your own servers (it turns service requests on by itself). Create its address with `/ad pq_ratchet=on`, take the short link, and point `DIRECTORY_SERVICE_LINK` at it in your local build only. Do not commit that change. +3. **Directory content** (most useful with a test directory): + - at least one listed channel and one listed regular group; + - enough entries for more than one page (entries with large images fit about one per page); + - one entry with a long name and a two-line description; + - one entry whose name contains markdown, e.g. `*bold* [x](https://example.com)`; + - one channel the test profile has already joined. +4. **SimpleX names** (for the Names cases): the test profile needs a server that resolves names. You also need a channel with a verified `#name` and a contact address with an `@name`, each at least 5 characters long (a bare word is completed with `.testing`). Without these, only cases N4 and N7 can run. +5. **Consent alert:** Settings → "Reset all hints" brings it back between runs. +6. Run every chat-list case in **both toolbar modes** (one-hand UI on and off). + +## Chat list: search rows and consent | ID | Do | Expect | |----|----|--------| -| S1 | Look at the screen: `ui tree`, then `ui shot S1-` | The chat list of a profile without conversations: the heading "Talk to someone", the cards "Let someone connect to you" and "Connect via link or QR code", and the toolbar with your profile image (`Image "Account"`, 32 x 32) and "Compose": at the bottom of the screen with the one-hand UI, at the top with the top bar | -| S2 | Tap "Let someone connect to you", then "Back" | The page "Create your link" with the cards "Invite someone privately" and "Create your public address" and a "Back" button; after "Back", "Talk to someone" again | -| S3 | Open the profile picker by tapping the toolbar's profile image, tap "Settings", then close Settings by swiping it down | The picker shows your profile name and "Settings"; Settings opens with "Your settings" and "Network & servers"; after closing, "Talk to someone" again | +| C1 | Empty search field | Tags in the slot above the field (one-hand) or above as usual (top bar); no "Search in Directory" row | +| C2 | Type `ne` | One-hand: "Search in Directory" replaces the tags above the field. Top bar: tags stay, "Search in Directory" appears below the field | +| C3 | Type a 5+ letter word, e.g. `alice` | "Connect to alice.testing" and "Search in Directory" (same slots as C2) | +| C4 (AC4) | Paste a SimpleX link | One-hand: the **tags stay** in the slot (before this change the slot was empty). Neither mode offers "Search in Directory"; the link connects as before | +| C5 (AC8) | After resetting hints, tap "Search in Directory" | Alert "Search in Directory?": the text goes to SimpleX Directory, and if it is a SimpleX name, also to your server that resolves names. Actions: Cancel / Search / Search and don't show again | +| C6 | In C5, choose Cancel | Nothing is sent: no spinner, no results | +| C7 | Choose "Search and don't show again", then search again | No alert the second time; "Reset all hints" brings it back | +| C8 | Type text, press the keyboard **Search** key | The key is labelled Search; it runs the same directory search (alert first time) | +| C9 | Whitespace only, then press Search or tap the row | Nothing happens | + +## Chat list: directory results (AC1–AC3) + +| ID | Do | Expect | +|----|----|--------| +| D1 | Search for a term that matches listed groups | Spinner in the search field after about 1 s. While loading: **no "Directory" header**, no "No chats found" overlay | +| D2 | Results arrive | A "Directory" header row, then rows: avatar, `#` name, the directory's SimpleX name (if any), description up to 2 lines, member or subscriber count, and **"Join channel"** for channels or **"Join group"** for groups at the trailing edge | +| D3 (R1-4) | Long-named entry, small phone, large text | The verb stays on one line; the name and description truncate instead | +| D4 | Scroll the list, especially in one-hand mode | The "Directory" header scrolls with the rows and is not pinned to the screen edge | +| D5 | Entry with markdown in its name or description | Shown literally: no bold, no tappable link | +| D6 | Tap a directory row | The usual join flow (channel or group alert); the keyboard hides | +| D7 | More entries than one page | A "Show more" row; tapping it appends the next page, is disabled while loading, and disappears at the end. No entry appears twice | +| D8 | Turn networking off, search | After about 10 s: "Search failed, tap to retry" under the "Directory" header. Turn networking on, tap it: the page loads | +| D9 | Fail a "Show more" (network off), then retry | The retry continues from the next page; it does not start over | +| D10 (AC3) | Search for nonsense, e.g. `zqxvjk`, with no local chat matching | No Directory part, no "No results" row; the centred "No chats found" appears once the search finishes | +| D11 | Search for nonsense that matches a local chat name | Only the local chats; no Directory part, no overlay | +| D12 | Start a search, change the text before it returns | Results and spinner clear at once; the old reply never appears | +| D13 | Start a search, clear the field | The spinner stops; nothing appears later | +| D14 | With results shown, switch profile | Results disappear | +| D15 | Profile with no conversations (onboarding pages shown) | The search bar is above the onboarding pages. A search replaces them with the results while it runs or shows rows; a search that finds nothing shows a "No chats found" row; clearing the text brings the onboarding pages back | + +## Chat list: no duplicates (AC6) + +| ID | Do | Expect | +|----|----|--------| +| U1 | Search for the name of a channel you joined (it is also listed) | It appears once, as your local chat; no Directory row for it | +| U2 | Search for a word only in that channel's description, so the local list does not show it | It appears in the Directory part; tapping it opens the "already joined" alert for the known channel | +| U3 | A listed regular group you joined through a different link (or by invitation) | It shows twice, local and Directory. This is the documented limit, not a regression | + +## Chat list: names (AC5) + +| ID | Do | Expect | +|----|----|--------| +| N1 | Type a bare name registered only as a channel (e.g. `news1`), press **Search** | Directory results as above, plus a "Names" header above the Directory part with one row: avatar, profile name in bold, `(#news1…)` in grey, description. The "Connect to …" row disappears | +| N2 | Type `@alice.testing` (registered contact address), press Search | One Names row for the contact | +| N3 | Bare name registered as both a contact and a channel | Up to two Names rows | +| N4 (R1-6 note) | Name that is not registered, press Search | **No alert**, no Names row; "Connect to …" stays; directory results still come | +| N5 | Name of a contact you already have, shown in the local list | No Names row for it | +| N6 | A channel found both by name and in the directory | Shown once, as the Names row. If it also shows in Directory, note it (link encoding differs; nothing should ever be hidden wrongly) | +| N7 | Profile without a name-resolving server, press Search on a name | No "None of your servers…" alert; just no Names row | +| N8 | After names arrive, tap "Search in Directory", "Show more" or retry | The Names rows stay | +| N9 | Tap "Search in Directory" (not the key) with a name candidate | Directory only; no Names part | +| N10 | Tap a Names row | The usual connect flow (contact or channel alert). If the directory page is still loading, it stops, and the Names rows stay (also on the onboarding screen) | +| N11 | Start a connect (tap a result, "Loading profile…"), and while it runs press Search | The spinner stays the connection's; when both finish, no spinner is left behind | + +## New chat (AC7) + +| ID | Do | Expect | +|----|----|--------| +| W1 | Open New chat (pencil), type text | "Search in Directory" below the field; the action rows (1-time link, scan, create group/channel) hide as before | +| W2 | Type a 5+ letter word | "Connect to … " and "Search in Directory" below the field | +| W3 | Paste a link | No "Search in Directory"; the link connects as before | +| W4 | Press the keyboard **Search** key | "Names" and "Directory" sections after "Your Contacts", with uppercase grey headers like it; first time, the same consent alert | +| W5 | Search with no contact matching | "No filtered contacts" is hidden while searching and when there are results; shown when nothing is found anywhere | +| W6 | Tap a Directory or Names result | The sheet closes and the join or connect flow runs | +| W7 | Name of a contact listed in "Your Contacts" | No Names row for it | +| W8 | Name of a known contact whose display name does not contain the typed text | It shows as a Names row (documented gap); tapping opens the existing chat | +| W9 | Close the sheet while a search runs | The spinner stops; the chat list is unaffected | +| W10 (R1-1) | Sheet open with results, tap a notification from another profile | The results disappear | +| W11 | New chat → Archived contacts, type and press return | The key is "return", not Search; no online rows; empty states as before | + +## Regression checks + +| ID | Do | Expect | +|----|----|--------| +| G1 | Typing a known contact's SimpleX name on the chat list | The list narrows to it while typing, as before | +| G2 | Tag filters, unread filter, clearing the search | Unchanged | +| G3 | Tap the pencil while a chat-list search runs | The chat-list search is cancelled (spinner stops) | +| G4 | Pull to refresh (reconnect servers) | Unchanged |