Skip to content

Retain failed optimistic sends with visible failure reasons - #50

Merged
nixfred merged 1 commit into
nixfred:mainfrom
jondkinney:fix/send-failure-feedback
Sep 10, 2026
Merged

nixfred merged 1 commit into
nixfred:mainfrom
jondkinney:fix/send-failure-feedback

Conversation

@jondkinney

Copy link
Copy Markdown
Contributor

Failed sends currently lose their optimistic bubble. Retain the failed attempt with a bounded Not Delivered reason, distinguish sends by local ID, and reject thread snapshots that predate local send changes. Provisional bubbles remain in memory and never advance read marks.

Validation: bun test (440 passing). Shared Mac-side CI test suites and shell syntax checks pass. No real conversations were opened or messages sent for testing. Shellcheck is unavailable locally; CI covers it.

Based directly on current main (174b58e), as one focused commit; independent of the other split PRs and contact-write PR #25.

Keep rejected text attempts visible with a bounded Not Delivered reason across reloads. Give each send its own local ID and discard thread snapshots that predate a local send or failure, so same-second sends and late responses cannot erase each other.

Send-state logic is tested TypeScript with a generated QML module. Preserve stdin-only transport, memory-only message text, and exclusion of provisional bubbles from read marks. Verified with synthetic UI states and 403 tests.
@jondkinney
jondkinney requested a review from nixfred as a code owner September 10, 2026 03:23
@nixfred
nixfred merged commit 68a0d07 into nixfred:main Sep 10, 2026
1 check passed
nixfred added a commit that referenced this pull request Sep 10, 2026
A group with no name showed as a bare id with initials; it now names itself
from its participants and draws a composite avatar from their pictures.
Carries a real concurrency fix too: an avatarBusy flag holds the request
identity until stdout is consumed, so a queued avatar cannot be attributed to
the wrong handle.

The BlipView hunk did not merge honestly and I did not force it. Git aligned
the new Loader against a region main had restructured, producing a conflict
whose "theirs" side re-added the avatarMask and MultiEffect blocks that sit
directly above, plus the Text and ColumnLayout that sit directly below -
taking it wholesale would have duplicated three blocks in a 2600-line QML
file, which qmlformat would have happily parsed. Reset the file to main,
re-applied this PR's own diff with a three-way apply, and hand-placed the one
hunk that still would not land: the avatarCircleComposite Loader above the
existing initials Text, with that Text guarded by !avatarCircleComposite.active
so the initials stand down while the composite draws. Verified all four
intents are present and both composites are wired and guarded.

Deploy-line note, which is why this merged third: this PR adds GroupAvatar.mjs
but never updated the copy command. #50 and #52 had already moved CLAUDE.md,
AGENTS.md and CONTRIBUTING.md to `cp *.qml *.ts *.mjs`, so the gap is closed.
Merged alone and deployed by the book, this would have shipped a QML import
with no module behind it and killed both surfaces silently.

448 bun tests across nine files, every Mac-side suite, qmlformat on all QML.

LR-T: pr-55, group-avatar, composite, avatar-concurrency, merge-resolution
LR-D: blip, qml, collector, bridge
LR-K: a-conflict-that-duplicates-three-blocks-still-parses

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MjLJVjERSup2CMwVehKja7
nixfred added a commit that referenced this pull request Sep 10, 2026
Ctrl plus a digit opens the pinned conversation in that slot, following pin
order rather than list position, and it keeps working while an editor has
focus so it is reachable mid-draft. The conversation view gains a back arrow
for mouse users, which the keyboard already had through Esc.

Conflict: ui.test.ts, this PR's test block and #50's landing at the same spot.
Kept both, and the suite is the proof - a mis-spliced test file fails loudly
rather than silently passing.

450 bun tests, Mac-side suites and qmlformat green.

LR-T: pr-53, keyboard-shortcuts, pinned, back-arrow
LR-D: blip, qml, keyboard
LR-K: pin-order-not-list-position

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MjLJVjERSup2CMwVehKja7
nixfred added a commit that referenced this pull request Sep 10, 2026
ComposerInput.qml exposes a proper editable accessibility field and draws
spelling ranges supplied by spellcheck.ts. Draft text reaches the helper on
bounded stdin, never argv and never disk, and the helper returns only UTF-16
ranges - it never echoes the text back. That keeps the "message text never
lands on disk, never rides argv" invariant intact for a feature that has to
read every character you type.

The arrow keys go back to native text editing inside the composer, which is
what a text field should do.

Conflicts: CLAUDE.md, README.md and ui.test.ts, each an independent paragraph
or test block landing where #50 had just added its own. Kept both sides.

473 bun tests across twelve files, Mac-side suites and qmlformat green.

LR-T: pr-51, composer, accessibility, spellcheck, arrow-keys
LR-D: blip, qml, composer
LR-K: a-speller-that-never-echoes-the-text-back

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MjLJVjERSup2CMwVehKja7
nixfred pushed a commit that referenced this pull request Sep 10, 2026
test-setup.ts already points XDG_CACHE_HOME at a scratch dir so no test
touches the developer's caches. bridge.conf was not covered: contact-search
reads country_code= through XDG_CONFIG_HOME (falling back to ~/.config),
and the preload never set it. On a machine set up outside North America
(country_code=47) five tier2 cases failed — "(404) 555-0123" normalized
to +474045550123 — while CI, which has no bridge.conf, stayed green.

XDG_CONFIG_HOME now gets a scratch dir beside the cache one. One test
proves the isolation reaches the reader: the default country code is 1
in the scratch dir, and a bridge.conf written there is what changes it.
The test first asserts the variable is set, because without it the
scratch path is the literal ./undefined/, which is how stray artifacts
of that name get committed.

undefined/st.json is removed again: 7d569c3 took it out after 2.2.0 and
#50 brought it back. It is the collector's state file written with an
unset directory variable — the same class of artifact this preload
exists to prevent.

Measured: five tier2 failures with country_code=47 before, none after,
on the machine that had them and against a fake home on a clean one.
nixfred added a commit that referenced this pull request Sep 13, 2026
… entry

Release audit before 2.5.0, same method as 2.4.0: every merge since v2.4.0
checked against Unreleased. Missing and now written: #73 (security code on
wl-copy stdin), #50 (failed send keeps its bubble), #51 (accessible composer,
spelling, arrow keys), #52 (resizable panel + compact header), #53 (Ctrl+1-9
pins, back arrow), #55 + 2197e0d (unnamed groups named after people,
composite avatar), #59/#60/#61 (contact scans, read-only card view, vCard
copy/save), #48 (window restores on its workspace), #36 (no uninvited consent
prompt from blip-check). Attributions checked against gh PR authors: #48 is
@jefehoser; #36 reported by @jacobaross. Skipped as not user-facing: #64
(test isolation), #65 (README line), CONTRIBUTORS docs.

No version bump yet: blip-b2 is landing the avatar-cache fix and #62 for
Fred, and 2.5.0 waits for those.

LR-T: changelog, release-audit, 2.5.0, attribution, backfill
LR-D: blip, docs, release
LR-K: audit-the-release-not-assume-it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016PeUtMhaSaA9aiazebBUMS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants