Repository navigation
Add the omaq-connect terminal client and prepare v1.0.0 - #60
Merged
Merged
Conversation
omaq-connect is a text-only OmaQ client for any terminal. It talks to the helper over the existing socket like Service.qml, so the bar and the helper stay unchanged. - Direct chats with online state, typing, receipts and read sync - Split views, :help, :quit/:qa and suggestions while typing a command - Starts the helper when none runs and stops only a helper it started - --link/--unlink manage ~/.local/bin/omaq-connect; the installers link it and the uninstaller removes a dangling link - Tests: unit, pty, and two- and three-identity Tox end-to-end runs; the uninstall test now uses an isolated HOME
The composer picker now shows the 22 reaction emoji followed by 🤟 (composerEmojiSet). Reactions stay limited to the 22 emoji the helper accepts. 🤟 has no bundled image, so the picker uses its existing Noto Color Emoji text fallback. Update the reviewed plaintext-policy hashes for pages/ChatPage.qml. The computed writes and setProperty calls are unchanged.
- Friends: :invite with QR code and clipboard copy, :invite revoke, :redeem, :requests, :accept and :decline - Input history, :clear, 15-second confirmations and a blinking cursor - Focus marker and name pill, unread highlight and message preview - Emoticons become emoji, Ctrl+K emoji picker, and emoji widths are measured at startup - Text size shown after a terminal zoom, and rotating tips - Fixes from the Fable 5.1 review: confirmation expiry, identity_changed broadcasts, linear composer scrolling, bar wording and errors, QR colors, clear correlation, picker search, friends-bar overflow, long links and the invite countdown - README section, illustrated user guide section and stage notes
- :sendfile <path> sends a file to the open chat. Relative, ~ and quoted paths work, and one file per chat travels at a time, as in the bar. - Incoming files stay paused until accepted: the chat shows the offer, :acceptfile saves it in the helper's download folder (~/Downloads/omaq by default), and :declinefile rejects it. - :cancelfile stops an outgoing transfer, waiting for the transfer id when needed. - Results use the bar's wording; the friends bar highlights a friend with a pending offer whose chat is off screen. - Test helpers save downloads inside their own state folder, and the two-home test sends and receives real files and compares the bytes. - A unit test keeps the wording copied from the bar in sync.
Both make test and make test-ci now run tests/cli_unit_test.py, so a change that breaks omaq-connect fails the regular suite.
- The helper emits file.progress for direct transfers (never avatars) with done and size, at most every 250 ms and only when the percentage changes, so a file sends at most about 100 events. - omaq-connect draws it as a bar on the transfer line, for example "Sending notes.pdf ███████░░░ 70%", and keeps the plain line until the first event or with an older helper. Events must match the transfer id or request and the friend's key. - The bar ignores the event for now. - The file transfer test checks the size lookup; the two-home test sends a real 1.5 MB file and checks bytes, rising progress, and the drawn bar.
Terminal client: - Input recall, the clear confirmation, offers and sends are bound to the contact's stable key. Rebinds, identity changes, locks and disconnects drop them, and a nospam change no longer resets state. - The confirmation prompt stays visible. Notices that arrive behind it are shown with its result instead of expiring unseen. - Messages with unknown delivery, failed messages, and messages that were sent but not saved survive history reloads. The bar's note "Message sent, but could not be saved locally." is shown. - A reconnect to the same helper keeps transfers and checks them with file.status; incoming offers are confirmed through file.state. File commands act on the focused chat only. - Foreign errors are no longer taken as the client's own. Accept and decline use their own request id and the helper's contact.decided result. - A :quit during startup starts no helper, and the handshake is finished before quitting. The client says when it cannot confirm that OmaQ went offline. Exits are serialized per state directory. - Small windows wrap safety codes by group and scroll help and overlays; Enter never runs a hidden help entry. Helper: - A cancel from the peer only drops local state. The extra Tox cancel was echoed back by toxcore and killed the next transfer in the slot. - A failed direct file.accept sends file.failed (accept_failed, or transfer_unknown when the offer is gone). - file.status with an incoming transfer id answers with file.state. - Every contact.decide ends with a correlated contact.decided event. - The progress throttle lives in file.c and is cleared when a transfer ends. Tests cover each finding: unit tests, the file transfer C test, the pty test, and the two-home test, which now resends right after a decline in both directions.
cli/omaq.py is now only the entry point, with the loader, link management and main(). The client code lives in cli/omaq_connect/. The loader imports the package by path, so it also works under python3 -I. It sets sys.dont_write_bytecode first, so no __pycache__ lands in the installed plugin tree, which the updater requires. The installer fixture now copies the package, a new test checks that --help leaves no bytecode cache, and CI compiles cli as well.
These parts move out of the package root unchanged: - text and width handling - the helper vocabulary - palette, key decoding and layout - the socket link and the presence locks - terminal probes, helper process paths and system helpers tests/cli_arch_test.py checks the import direction between modules. It keeps socket, terminal, process and signal modules out of the pure ones, and it forbids "from time import"-style imports that tests could not patch. make test runs it.
The 334-line on_event chain and the command chain become tables from names to methods. The handler bodies are unchanged. Guard conditions stay in the handlers, and malformed event names are ignored as before.
FileTransfers owns offers, sends and the last result. It reaches the rest of the client only through send, say, the friend list and a reload callback. The friend binding rules become pure functions in session.py.
Invites owns the invite link, the pending request, the request ids and the action window. Overlays and the revoke confirmation stay in the user interface and are reached through callbacks. Error handling keeps its order.
History merging, finding a sent message, the unsaved-message note and rebind detection become pure functions with their own tests.
"File transfer successful" now stays in the status line for 7.5 seconds. Warnings keep 15.
Rendering moves into view.py as Screen: the friends bar, panes, messages, transfers, overlays, help, picker and hint line. Screen reads state through the app and does no I/O. The command, key and tip tables move into commands.py.
FileOffer and OutgoingSend replace the offer and send dicts. They keep their own progress and status rules. Tests change only from item to attribute access.
Invite and PendingRequest replace the invite and request dicts. The invite knows whether it is still active.
Confirmation replaces the confirmation dict. It knows when it expires and what it says when the answer is no. It starts the new modals.py.
Help, Picker and the invite, request and redeemed overlays share handle(app, kind, value) and rows(screen, width, height). The overlays share paging and Escape through an Overlay base class. App.edit keeps the input order: lock, help, confirmation, picker, overlay. A help list opened again now starts without the visible range of the previous one. Enter before its first frame no longer runs a command whose row has not been drawn yet. This matches the first opening and F06. A new test pins it.
Command parsing, colon suggestions and tab completion are now pure functions next to the command tables. Tab completion keeps its state in a Completion dataclass.
Conversation replaces the per-chat dict for items, loaded state, history request and typing time.
Chat owns conversations, pending messages, unread counts, read marks, history retries and clear requests, along with their events. App keeps panes, focus and notices. The event table now names the owning object, such as chat.on_history or files.on_offer, which removes 16 forwarding methods. A test checks that every table entry resolves.
HelperProcess starts the helper, reports a failed start from its log and decides whether this client launched the running helper. App keeps the connect, settle, exit and main loop flow.
Pane now owns typing, cursor keys, deletion and recall of earlier lines. The layout keys become one table in keys.py. The emoji picker inserts through the same path.
The properties from the first split are gone. Code and tests now use app.files and app.invites directly.
App, its tables and its constants move into app.py with explicit imports. __init__.py only re-exports the modules. The import rule test covers app.py as the composition root.
The test read Panel.qml, ChatPage.qml and Service.qml without closing them, so every run printed three ResourceWarnings.
test_r12_small_windows_wrap_codes_and_scroll looked for "ctrl+n" in the whole frame. The bottom line starts with a random tip, and one of the 21 tips names ctrl+n, so about one run in 21 failed. The test now looks for the help text "New pane with the next friend", which no tip uses, and passes with every initial tip.
:nickname <name> sends the helper's nickname.set with the client's own request id, like the bar's nickname field. The client first checks the name the way the bar does: 1 to 18 characters, no control characters. :nickname alone shows the current name, taken from the snapshot and from nickname events. Results, errors and the 10-second timeout use the bar's wording. The command needs the full word. A new Profile class in profile.py holds this state. The unit test covers the name check, correlation, errors, the timeout and a dropped link. The PTY test sets a name through the real helper and finds it again after a restart.
terminal-client.md gains a Structure section. It covers the entry point, the module groups, the import rule and its test. Its first entry names the package, and How to check lists tests/cli_arch_test.py and the current unit test count. CURRENT.md mentions the checked import rule.
Sources of truth now lists cli/ as one more presentation adapter on the helper socket. The entry says the client keeps its state and presentation modules free of terminal, socket and process I/O, and must not become a second protocol implementation.
Security now follows the introduction, with the message-flow diagram folded. A two-column comparison with linked screenshots replaces the four bar screenshots. It explains that both clients share one identity, contacts and history, that the terminal client runs without the bar, and that the updater needs the plugin enabled. Install, Update and Uninstall are shorter. Uninstall now mentions the omaq-connect command. The project policy is folded after Uninstall, and the AUR note is gone. tests/update-order.sh now checks the two comparison columns instead of four screenshot cells.
The uninstaller deletes ~/.local/bin/omaq-connect only when it is a dangling link into the removed hancore.omaq plugin. Four new cases run the real script: a dangling link into the plugin is removed, while a link to a tree that still exists, a foreign link and a regular file stay.
After the bar is disabled, the helper it started keeps running until the user logs out. Quitting the terminal client therefore takes you offline only from the next login on. A user-like run on a second Omarchy machine confirmed this.
A cancellation by either side now leaves "✕ File transfer canceled: name" in that chat, like the bar's dismissible file status. The line is bound to the contact's key. Esc on an empty input line hides it. A new offer or send, another result, or a rebound number replaces it. The short status-line notice stays as before.
Cursor keys, backspace and delete now follow the clusters the screen draws. In terminals that draw ZWJ and skin-tone sequences as one cluster, those sequences move and delete as one unit. Elsewhere they still go piece by piece, as drawn.
The manifest moves to 1.0.0, still Protocol 16. CURRENT.md, the stage index and the terminal-client stage now name 1.0.0 where they said unreleased or feat/cli. The CURRENT.md date moves to 2026-10-11.
ShellCheck flags `A && B || C` as SC2015, and CI fails on it. The check is now an explicit if with the same logic.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
:nickname;:help.file.progressevents.file.failedwhen a direct accept cannot start, afile.stateanswer for incoming transfers, andcontact.decidedfor every decision.install.shcreates~/.local/bin/omaq-connect, and the uninstaller removes it together with the plugin.tests/cli_arch_test.pychecks the import rule.Verification
make test,make helper,make arch, the no-Signal build, phase 2, phase 8,qmllint,omarchy plugin validateandgit diff --checkall pass.