This was an interop bug, not a missing feature. The engine has parsed editOf, pins and fwd for as long as they have existed, and it exposes isRenderable — "does this belong in a thread" — as the single answer. Session's inbound loop never consulted it.
So three things arrived from the iPhone app and were filed as ordinary bubbles.
An edit carries its new wording in text, so it drew as a DUPLICATE beside the
typo it was meant to replace — a correction that made the thread worse than the
mistake did. A pin carries only an id, so it drew as a blank bubble. And a
forward was drawn as the sender's own words.
Worst of the three is the one nobody had hit yet: a control envelope from a
NEWER peer carries ctl and a kind this build has never heard of, and would
have drawn a bubble full of JSON in somebody's conversation. That is precisely
the failure isControl was written to prevent, defeated by nobody calling it —
and it would have shipped to every older Android install the first time a
control kind was added on iOS. The guard is isRenderable rather than a list of
kinds this build knows, because an allowlist is only correct until the other
platform adds one.
What is applied now:
an edit rewrites the message it names and stamps edited, so a bubble can
say so — silently replacing the text is worse than the typo, because the
reader cannot tell a correction from their own misremembering;
a pin is stored per thread, sealed in the vault like the transcript, as an ID
and never a copy: a copy goes stale against an edit and outlives a deletion;
fwd reaches the transcript so second-hand words are not drawn as the
sender's own — THAT it was forwarded, never from whom.
An inbound edit is applied only when it comes from the person whose message
it is. Without that check any contact sharing a session could rewrite anybody's
words in the reader's transcript — a better attack than sending a message of
their own, because the words would carry somebody else's name.
Clearing a thread clears its pin. A banner pointing at a message that was
just cleared reads as the clear not having worked.
fwd is read as both a boolean and a number, for the same reason isControl
accepts both: org.json's optBoolean does not coerce a number, and that is
how this class of bug shipped once already.
9 unit tests. App builds, every JVM suite green. The three PARITY rows stay :
the inbound half is done, originating each still needs the UI.
This was an interop bug, not a missing feature. The engine has parsed
`editOf`, `pins` and `fwd` for as long as they have existed, and it exposes
`isRenderable` — "does this belong in a thread" — as the single answer.
`Session`'s inbound loop never consulted it.
So three things arrived from the iPhone app and were filed as ordinary bubbles.
An edit carries its new wording in `text`, so it drew as a DUPLICATE beside the
typo it was meant to replace — a correction that made the thread worse than the
mistake did. A pin carries only an id, so it drew as a blank bubble. And a
forward was drawn as the sender's own words.
Worst of the three is the one nobody had hit yet: a control envelope from a
NEWER peer carries `ctl` and a kind this build has never heard of, and would
have drawn a bubble full of JSON in somebody's conversation. That is precisely
the failure `isControl` was written to prevent, defeated by nobody calling it —
and it would have shipped to every older Android install the first time a
control kind was added on iOS. The guard is `isRenderable` rather than a list of
kinds this build knows, because an allowlist is only correct until the other
platform adds one.
What is applied now:
- an edit rewrites the message it names and stamps `edited`, so a bubble can
say so — silently replacing the text is worse than the typo, because the
reader cannot tell a correction from their own misremembering;
- a pin is stored per thread, sealed in the vault like the transcript, as an ID
and never a copy: a copy goes stale against an edit and outlives a deletion;
- `fwd` reaches the transcript so second-hand words are not drawn as the
sender's own — THAT it was forwarded, never from whom.
An inbound edit is applied only when it comes from the person whose message
it is. Without that check any contact sharing a session could rewrite anybody's
words in the reader's transcript — a better attack than sending a message of
their own, because the words would carry somebody else's name.
Clearing a thread clears its pin. A banner pointing at a message that was
just cleared reads as the clear not having worked.
`fwd` is read as both a boolean and a number, for the same reason `isControl`
accepts both: `org.json`'s `optBoolean` does not coerce a number, and that is
how this class of bug shipped once already.
9 unit tests. App builds, every JVM suite green. The three PARITY rows stay :
the inbound half is done, originating each still needs the UI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
This was an interop bug, not a missing feature. The engine has parsed
editOf,pinsandfwdfor as long as they have existed, and it exposesisRenderable— "does this belong in a thread" — as the single answer.Session's inbound loop never consulted it.So three things arrived from the iPhone app and were filed as ordinary bubbles.
An edit carries its new wording in
text, so it drew as a DUPLICATE beside thetypo it was meant to replace — a correction that made the thread worse than the
mistake did. A pin carries only an id, so it drew as a blank bubble. And a
forward was drawn as the sender's own words.
Worst of the three is the one nobody had hit yet: a control envelope from a
NEWER peer carries
ctland a kind this build has never heard of, and wouldhave drawn a bubble full of JSON in somebody's conversation. That is precisely
the failure
isControlwas written to prevent, defeated by nobody calling it —and it would have shipped to every older Android install the first time a
control kind was added on iOS. The guard is
isRenderablerather than a list ofkinds this build knows, because an allowlist is only correct until the other
platform adds one.
What is applied now:
edited, so a bubble cansay so — silently replacing the text is worse than the typo, because the
reader cannot tell a correction from their own misremembering;
and never a copy: a copy goes stale against an edit and outlives a deletion;
fwdreaches the transcript so second-hand words are not drawn as thesender's own — THAT it was forwarded, never from whom.
An inbound edit is applied only when it comes from the person whose message
it is. Without that check any contact sharing a session could rewrite anybody's
words in the reader's transcript — a better attack than sending a message of
their own, because the words would carry somebody else's name.
Clearing a thread clears its pin. A banner pointing at a message that was
just cleared reads as the clear not having worked.
fwdis read as both a boolean and a number, for the same reasonisControlaccepts both:
org.json'soptBooleandoes not coerce a number, and that ishow this class of bug shipped once already.
9 unit tests. App builds, every JVM suite green. The three PARITY rows stay :
the inbound half is done, originating each still needs the UI.
Co-Authored-By: Claude Opus 5 noreply@anthropic.com
assigned to @paulhitt
mentioned in commit
8466fc1b57changed the description