The one-hand chat list is flipped, so a header emitted before its rows was drawn below them. searchPart puts the header after the rows in that layout; the rows keep their order, nearest the search field. Spec links into ChatListView.swift refreshed.
The admin was contact 3 of the directory, not 2 (run 38713931270), and a super user is matched by contact ID and name. The service now starts with a placeholder super user, the admin connects, and the service restarts with the ID read from its database. This run tests the group and channel registration, the CLI search, the app build and the smoke-test search.
The macos-26-arm64 image 20261013 adds Command Line Tools 27, and links resolved system libraries in its macOS 27 SDK, which the linker rejects (tapi error: unknown architecture arm64e.x1-macos; run 38702428043). DEVELOPER_DIR points the xcrun shims that GHC and prepare-libs.sh call at /Applications/Xcode.app. The Show runner steps print the SDK in use. This run tests the backend build on the new image; core comes from run 38702428043's artifact.
Every run waited for macOS runners three times (core, backend, app), even
when core and backend only restored a cache, and those caches are evicted
within hours here. A plan job on ubuntu names both artifacts after the hash
of their sources and finds them in earlier runs of this branch in this
repository (actions: read); core and backend build only when none exists, and
the app job downloads from the run that built them. Artifacts are kept 30
days; the zip caches are dropped.
This run tests: the plan job, building and uploading both artifacts under
their new names (none exist yet), and the app job downloading them; then the
directory start with -y, the seeding, and the search in the app.
setup-haskell reports ~/.cabal/store, but without ~/.cabal cabal 3.10 keeps its store in XDG directories, so the saved cache held only dist-newstyle (run 38563035327: 401 MB). CABAL_DIR is set before Setup Haskell, so its cabal update, the build and the cache path agree. build-backend.sh prints the store size before building, as build-core.sh does.
The database is created by the CLI, so the service's own migration (20250924_directory_schema) asked for confirmation on stdin and the service exited. This run tests that the directory starts and the group and channel get listed.
The app is built with DIRECTORY_SERVICE_LINK set to the test directory's
short address (sed in the workflow). The smoke test types "ci seed", taps
"Search in Directory", accepts the first-search consent alert with "Search",
and expects the Directory results with #ci_group (Join group) and
#ci_channel (Join channel), retrying once if the search fails.
The steps now use scripts/ci/ios-sim/ui, which wraps AXe (tap by label or
value, type, wait, screenshot, compact accessibility tree) for scripts and
for an agent on the runner. Later steps get SIM_UDID and UI_OUT.
This run tests: the onboarding and typing steps on the new helper, the
consent alert, and the directory results in the app.
hashFiles fails on apps/simplex-directory-service/web/directory.js, a symlink to website/src/js/directory.js, which is not in the repository; the web folder is not part of the build. This run tests that the backend job computes its key and builds.
A new backend job builds simplex-chat and simplex-directory-service with the
server-capable configuration. The app job starts a chat relay and a directory
on the preset servers, registers and approves a group and a channel from a CLI
peer, and checks that a service request for "ci seed" finds both, before the
app is built. The app still uses the production directory link; the smoke
test is unchanged.
The cabal store cache now uses setup-haskell's store path, with a key prefix
per job, so rebuilds after Haskell changes reuse built dependencies.
This run tests: backend build, relay and directory start, registration and
approval over the preset servers, the service request, and that M2 still
passes.
Run 38448094338 reached the chat list and typed into the search field, which showed "Ci smoke" and the Search in Directory row, but the check looked for "ci smoke": the keyboard capitalizes the first letter. SwiftUI text fields expose no accessibility label, only the placeholder as the value of an empty field, so the fields are now tapped by value.
This run tests the smoke test end to end, with the core library restored from cache.
Adds the ios-sim workflow, triggered only by pushes to f/ci-ios-sim and by workflow_dispatch.
The core job builds lib:simplex-chat with cabal using the flake's iOS settings (swift JSON, client library, static roll-up archive, threaded RTS, CommonCrypto), packages it as the Hydra zip, and patches it for the simulator with scripts/ios/prepare.sh and mac2ios. The app job builds "SimpleX (iOS)" for the arm64 simulator against it and drives onboarding, the chat list and the search field with AXe, saving screenshots and accessibility trees.
This first run tests the whole pipeline, mainly whether the core library builds and links.
apps/ios/CODE.md asks that every #Lxx-Lyy reference in an affected
spec document point at the right lines. Most of these were already
stale on master; this corrects them in the documents the online search
change touched. Numbers only: no wording changes. Kept apart from the
feature documentation so it can be reviewed or dropped on its own.
Run: 002-ios-directory-search-ui, review round 1 (R1-7)
apps/ios/CODE.md asks every iOS change to keep spec/ and product/ in
step with the code. The online search (SimpleX Directory and names, on
the chat list and in New chat) had no documentation, including the
parts added on this branch before: the spec gains a chat-list section
and a New chat section, the API entries for the service request and
the connection plan's failure mode, the impact and index rows and the
Document Map rows for the new source files; the product docs gain the
views, a concept row, rules for what the search may and may not do,
and the known gaps. Only line references moved by this branch's own
code are updated here.
Run: 002-ios-directory-search-ui, review round 1 (R1-7)
R1-1 New chat clears its online results on a profile switch, as the chat list does
R1-3 the search spinner follows its two flags through property observers
R1-4 the join verb keeps one line, so the name truncates instead
Run: 002-ios-directory-search-ui, review round 1
The New chat sheet gets the chat list's online search: a "Search in
Directory" row, the keyboard Search key for names and the directory,
and Names and Directory sections after the contacts. Tapping a result
connects and closes the sheet, as other New chat actions do. A contact
the list already shows is not repeated as a name result, and the
contacts list's empty state stays hidden while a search runs or shows
results. Archived contacts, which share the search bar and the list,
are unchanged.
Run: 002-ios-directory-search-ui, slice 4
The keyboard Search key on the chat list now also resolves a typed
SimpleX name online, alongside the directory search, and shows what it
finds as a Names part between the local chats and the directory
results. Resolution never raises an alert: apiConnectPlan gains an
onFailure mode, and existing callers keep alerting by default.
Each directory entry is looked up in the store (store-only, as entries
are now group or channel links only) so a chat the user already sees
locally, or found by name, is not shown again; names already in the
local list are hidden the same way. A directory row is matched to a
name only by the link the core resolved, never by a profile field the
name's owner writes, so a name cannot hide a genuine directory row.
The "Search in Directory" row, retry and "Show more" keep the names
already shown. One helper drives the spinner from whether anything is
in flight, and every publish checks the search generation, so a late
reply after a text change or cancel writes nothing. The first-search
alert now also names the server that resolves names.
Run: 002-ios-directory-search-ui, slice 3
A directory result is untrusted input: an entry is kept only when its
link is a group or channel link, because the core fetches a one-time
invitation link even when asked for a store-only plan, and the next
slice looks every entry up in the store. The entry's link is no longer
optional, so the tap needs no unwrap and the id no fallback.
The Directory part now renders only when it has a row: no header while
loading, no "No results" row; the empty-state overlay shows when the
search finished with nothing. Its header is a row rather than a sticky
section header, which in the flipped one-hand list would pin at an
unverified position. Rows say "Join channel" or "Join group". A pasted
link in one-hand mode leaves the tags in place.
Run: 002-ios-directory-search-ui, slice 2
The search model is about to hold name results beside directory
results, so it becomes OnlineSearchModel. The "Show more" and retry
rows, the first-search consent check and the contacts list's shown-chat
derivation move to where the New chat sheet can reuse them. No
behaviour change.
Run: 002-ios-directory-search-ui, slice 1
R1-1 add the service response to the exhaustive iOS and Kotlin response matches
R1-3 directory search cursor carries only the sort value of its mode, so the RPC no longer sends registration times
R1-4 a directory search does not take the progress spinner from a connection
R1-6 directory entry type, name and rounded times defined once for listing and RPC
R1-7 restore the directory test harness signatures of master, with link-pair variants for the new tests
R1-8 tests: compressed service request through SREQ, reject without a reason
* core: release core library to simplex-chat-libs every time cabal version changes
* implement
* test build
* update libs release
* test library release
* create release commit, so the date is correct
* rework to create release in advance in maybe-release job
* remove test build
* move postgresql job back
* test build
* remove test build
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* rfc: invite member of one group to another group
* update
* update
* implement
* version
* update
* do not show error if contact was removed when joining group
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
* ios: delete temp data from storage view
The temp folder is the XFTP agent work directory and holds scratch
files for received-file encryption and roster transfers, so deleting it
under a running chat breaks those writes mid-flight. The button is only
enabled while chat is stopped, the same precondition core deleteStorage
relies on, and the row says how to stop chat.
* ios: show delete temp data alert over settings sheet
AlertManager presents from ContentView, which is behind the settings
sheet, so the confirmation never appeared. The alert now also warns
that interrupted transfers never complete and that videos in unsent
drafts, which live in the temp folder, are lost, and the caption names
the screen where chat is stopped.
* ios: say unsent draft videos are sent without the file
A draft video whose converted file was deleted from the temp folder is
still sent, as a preview with no file, so "lost" understated it.
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
* android: recognise passphrase fields as passwords for password managers
Passphrase fields used a hand-rolled VisualTransformation lambda, so Compose never
marked them as password fields: the autofill structure carried no hints and no
password inputType, and password managers could not identify them. The raw
passphrase was also exposed through the CopyText/ACTION_COPY accessibility action.
PassphraseField now declares an autofill content type and uses
PasswordVisualTransformation, which sets the password semantics Compose derives
that flag from.
Fields that create a passphrase declare NewPassword. Where no field would carry
plain password (first-time setup, hidden profile password) they declare both, as
managers do not recognise a form whose every field is newPassword. On the change
form the current-passphrase field supplies password, so the new fields declare
only newPassword - adding password there leaves the manager unable to tell which
field holds the new value and suppresses the save prompt.
* android: do not offer to save hidden profile passwords
Hidden profile password fields no longer declare an autofill content type.
Compose commits the autofill session, which shows the password manager's
save prompt, only after the last field with a content type leaves the
screen, so these fields never trigger it. A saved entry would reveal that
the hidden profile exists.
Rewrite the plan to match the code.
* android, desktop: storage breakdown in developer options
Port of the iOS Storage screen (#5529): lists each top-level entry of the app's data, config and temp folders with its recursive size, so it is possible to see what takes space.
* android, desktop: simplify storage view
Use DefaultProgressView, drop the bold total and redundant absoluteFile, and do not follow top-level symlinks (matches iOS; avoids counting the Android lib link).
* android, desktop: storage view: do not crash on names the JVM cannot convert to Path
Under a non-UTF-8 locale on Linux, File.toPath() throws InvalidPathException for a non-ASCII top-level name (e.g. a received file decrypted into tmpDir); log it and show 0 bytes. Also correct plan wording.
* android, desktop: storage view: fold path conversion into treeSize, use produceState
Also correct plan wording (du per-directory overhead, unlistable roots, Windows junctions).
* android, desktop: storage view: open as card screen like other settings screens
* android, desktop: storage view: list top-level entries as Path to keep their exact names
File.listFiles + toPath loses names that are not valid in the JVM file-name encoding (non-ASCII under a C locale, invalid UTF-8), so those entries showed 0 bytes. Files.newDirectoryStream keeps the raw bytes.
* android, desktop: storage view: normalize root paths before dropping nested roots
A root spelled with .. (e.g. XDG_DATA_HOME=/x/data/../tmp) compared as nested in another root and its size was shown nowhere.
* android, desktop: document storage view in spec and product docs
* android, desktop: storage view: single catch when listing a root
* android, desktop: do not log file paths in storage view
The walk's IOException messages contain the full path, which in tmpDir can be a received file's original name.
---------
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
The core allocates every chat_* response with malloc and passes ownership to
the caller, as iOS does in API.swift. Both JNI bindings copied the bytes into a
Java string and dropped the pointer, so every command, event and file read
leaked its whole response into the C allocator - invisible to both the JVM and
the Haskell heap profiler.
Desktop frees in decode_to_utf8_string, the funnel every entry point already
goes through. Android has no such funnel because NewStringUTF is called inline,
so decode_and_free is added and the call sites routed through it. chatReadFile
frees directly on both.
Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>
* core: group fixes/improvements
* Large messages dropped by relays and hosts (the same 15602 limit was applied, without accounting for forwarding header)
* Members below v18 did not receive forwarded messages or history in p2p groups.
* Join confirmations near the size limit could fail.
* SMP servers are limited to 2 host names each.
* simplify
* remove legacy forwarding and history support
* only restrict p2p groups
---------
Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>