minorsecond / minorsecond/AXTerm

Connected Chat “Atomic Messages” + Outbox Queue

Open
#42 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Swift
Stars
0
Forks
0
Avg merge
4d 2h
Merged PRs (30d)
1

Description

Goal

Fix the classic packet-radio annoyance where back-to-back sends feel glued together, ambiguous, or out of order. In AXTerm, user sends must be treated as atomic “messages” with a per-session outbox queue, explicit send/delivery state, and a clean “send line vs send block” compose UX — while remaining 100% interoperable with legacy stations.

Non-Goals

  • Do not require protocol changes on the remote station.
  • Do not implement encryption.
  • Do not require AXDP support (but design should be compatible with adding AXDP message TLVs later).

UX Requirements (Apple-ish, calm, clear)

Conversation UI
  • Each user send is rendered as a distinct bubble (or distinct message row) immediately upon Send.
  • Message rows show a subtle state indicator:
    • QueuedSendingSent (link-acked) → Failed
    • If retransmits occur: show Retrying… without being noisy.
  • If the user sends two messages quickly, they should never visually merge in the UI.
Compose Box
  • Provide a compose box supporting multi-line entry.
  • Keyboard behavior:
    • Enter: Send Line (default behavior for packet “chat”)
    • Shift+Enter: Insert newline (compose without sending)
  • Provide a segmented control or dropdown:
    • Send Line
    • Send Block
  • “Send Block” sends the entire compose buffer as a single atomic message (with line endings preserved).
Accessibility
  • State indicators must have accessible labels (“Queued”, “Sending”, “Sent”, “Retrying”, “Failed”).
  • No color-only meaning.

Technical Requirements

Data Model

Create a persistent model (GRDB) to represent atomic outbound messages:

OutboundMessage

  • id (UUID)
  • sessionId (connected session identifier)
  • destCallsign (string)
  • createdAt (date)
  • payload (string or data)
  • mode (line | block)
  • state (queued | sending | sent | retrying | failed)
  • attemptCount (int)
  • lastError (string nullable)
  • bytesTotal (int)
  • bytesAcked (int) (best-effort; see below)
  • sentAt (date nullable)
  • ackedAt (date nullable)

Note: If we cannot reliably compute bytesAcked, keep it at 0 and still progress state based on link-layer signals we do have.

Outbox Queue (per connected session)

Implement an OutboxManager that:

  • Maintains a FIFO queue per sessionId.
  • Immediately inserts a message row as queued on Send.
  • Transmits messages serially per session (no interleaving sends from the same session).
Flush / Serialization Policy

Add a setting (with a sane default) controlling when to send the next queued message:

Setting: Connected Send Serialization

  • Default: Practical
  • Options:
    1. Conservative: send next message only after prior message is fully link-acked (or otherwise considered safely out of the retransmit window).
    2. Practical (default): send next after at least one successful link-ack event after starting prior message (or after a short “TX drain” heuristic).
    3. Fast: send next immediately (still queued + atomic in UI).

Implementation must be robust even if the underlying stack doesn’t expose perfect “fully acked” signals.

Line Ending / Delimiting Rules (Legacy Safe)
  • Ensure each atomic send ends with exactly one CR (unless user explicitly includes final newline in block mode).
  • In Line mode:
    • Always append CR.
  • In Block mode:
    • Normalize internal newlines to CR where appropriate for legacy packet terminals OR preserve as-entered but ensure final CR.
    • Choose one approach and document it; must not create CRCR spam.
Error Handling
  • No hard-fail if a message can’t be sent immediately (e.g., session is temporarily not writable).
  • On send failure:
    • Mark message failed, store lastError.
    • Provide a contextual action: Retry, Copy, Delete.
  • If a session disconnects with queued messages:
    • Keep them queued and show “Waiting for reconnect” OR mark them failed with a clear reason (pick one behavior; prefer “queued until reconnect” if feasible).
Observability / Logging
  • Add structured logs for:
    • queue insert
    • dequeue / start send
    • retransmit detected
    • ack signal observed
    • completion / failure
  • Logging must not spam; use debug-level for per-frame chatter and info-level for state transitions.

Integration Points

Packet Engine / TNC Layer
  • Provide a single “send atomic payload” entry point used by UI:
    • sendOutboundMessage(sessionId, data) which enqueues and returns OutboundMessage.id.
  • Add hooks/callbacks from the AX.25 connected session layer for:
    • didStartTransmit(messageId) (best-effort)
    • didObserveAckProgress(sessionId) (best-effort)
    • didFailTransmit(messageId, error)
    • didDisconnect(sessionId, reason)

If the underlying layer cannot tag frames to a specific message, use a session-scoped heuristic:

  • While a message is sending, any ack progress event advances it toward sent.
  • Conservative mode may require “idle window” detection (no outstanding unacked I-frames for N ms) if available.

UI Details (Delicious)

  • Use subtle SF Symbols for state (examples; final choice up to you):
    • Queued: clock
    • Sending: paperplane
    • Retrying: arrow.triangle.2.circlepath
    • Sent: checkmark
    • Failed: exclamationmark.triangle
  • Hover / secondary-click on an outbound bubble shows actions: Retry / Copy / Delete.
  • “Sent (link-acked)” tooltip explains: “Acked by link layer; remote display not guaranteed.”

Acceptance Criteria

  1. Sending two messages back-to-back results in two distinct UI rows with independent state.
  2. Messages are sent in order for a given connected session.
  3. Default “Practical” serialization prevents obvious “glued together” behavior on typical links.
  4. “Fast” mode preserves current behavior but still keeps UI atomic.
  5. Compose UX works:
    • Enter sends line
    • Shift+Enter inserts newline
    • Send Block sends the entire buffer
  6. Failures are visible and recoverable (Retry works).
  7. No regressions for basic connected send/receive with non-AXDP stations.

Test Plan

Unit Tests
  • Outbox queue ordering per session.
  • State machine transitions:
    • queued → sending → sent
    • queued → failed
    • sending → retrying → sending → sent
  • Line ending normalization rules for line vs block.
Integration Tests (Simulated / Harness)
  • Simulate high-loss link with delayed acks:
    • verify message 2 does not start until policy allows.
  • Simulate disconnect mid-queue:
    • verify queued messages remain and UI indicates pending / failed per chosen behavior.
Manual QA
  • Connect to a legacy station/BBS and send:
    • two rapid lines
    • a multi-line block
    • rapid sends during busy channel
  • Verify readability and that AXTerm never merges sends in the UI.

Implementation Notes / Suggested Files

  • Models/OutboundMessage.swift
  • Database/Migrations/XXXX_createOutboundMessage.swift
  • Networking/OutboxManager.swift
  • UI/ConnectedChatView.swift (or equivalent)
  • UI/ComposeBar.swift
  • Settings/ConnectedSendSerializationSetting.swift

Future-Proofing (AXDP Ready)

  • Keep OutboundMessage.id stable and ready to map to future AXDP MSG_ID.
  • When both sides support AXDP, swap the “delimited text send” encoder to AXT1/MSG TLVs while keeping the same UI + outbox pipeline.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by reading the suggested entry points: Models/OutboundMessage.swift, Database/Migrations/XXXX_createOutboundMessage.swift, Networking/OutboxManager.swift, UI/ConnectedChatView.swift, UI/ComposeBar.swift, and Settings/ConnectedSendSerializationSetting.swift. Trace the current connected-send path before deciding how these pieces fit. Done means the acceptance criteria and listed unit, integration, and manual tests are satisfied without regressions for legacy stations.

Written by the indexing model from the issue text.

Assessment

Tech stack
swift
Domain
database, frontend, networking
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.