Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 55 additions & 0 deletions QORTIUM-HOME-CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,61 @@ both networks through explicit compatibility and security boundaries.

## Change Entries

### 2026-08-20 - fix(apps): preserve focus and permission sessions

Home 2 no longer hides and re-shows the active desktop app when routine node
telemetry refreshes, and a delayed native-view hide can no longer pull desktop
focus back to Home after the user moves to another window. Route-state updates
still reach hosted apps through a separate bridge-state delivery path. Missing
permissions from hidden app tabs are refused without switching tabs or raising
trusted Home chrome, while duplicate prompts are suppressed and session grants
are revoked only for the affected Home window, tab, account, or network. A
clearly disclosed tab approval for chat changes now covers sending, editing,
deleting, and reacting within the same public, direct, or private-group chat;
key management, publishing, administration, and all existing target, ownership,
route, signing, and rate-limit checks remain separate and unchanged.

A single clearly disclosed read-only account approval now covers the selected
Home account on both Qortal and Qortium for that app tab, including account
identity, direct messages, private groups, searches and attachments, and the
app's pending transaction records. It survives locking, unlocking, node
failover, and normal in-app navigation, while a real account change, tab
closure, or Home restart still revokes it. Unlocking and every mutation remain
separate. Restored app
tabs now show a neutral node-checking state until the first connection check
finishes, and unlock completion waits for the updated account state to reach
the app before the original operation resumes.

Private-group state now distinguishes node-level QPGC availability from
whether the selected account actually has the current group key. Desktop and
Android automatically create and announce a Qortium group key for all current
members when the first message, edit, delete, or reaction finds no usable key,
then continue the original operation without another app action or permission
prompt. Manual key controls remain a Qortal compatibility concern, not normal
Qortium user workflow. A newly announced or rotated key is kept unavailable
locally when its announcement broadcast is uncertain; Home records that control
signature while proving that the user's message was not submitted, so retrying
the message is safe and retained announcement discovery can reconcile the key.

Android Home 2 now keeps the node route selected by its portable Qortal/Qortium
connection client authoritative through public and direct chat, private-group
reads and writes, private attachments, and group membership or administration.
Those helpers no longer perform a second legacy Qortium node discovery or
borrow a legacy node API key, preventing valid requests from being rejected
when the two independent public-node selectors chose different healthy nodes.
Private-group state validation also stays on the dedicated permissioned vault
path instead of routing Home's own `/chat/private/...` request through the
generic app read allowlist, which intentionally excludes private API routes.
The Android connection client also retains a recently verified public route
through a brief failed health-probe cycle instead of reporting the network
unavailable between successful checks. Android's renderer policy now permits
only Home's same-origin memory-proof worker, allowing CHAT proof-of-work to run
without opening general network or cross-origin worker access.
The Android app bridge now also keeps every CHAT proof-of-work mutation open
for the same long-running window as ordinary message sends, so private-group,
direct, edit, delete, reaction, and key-management requests do not time out in
the hosted app while Home is still computing and may still broadcast them.

### 2026-08-19 - fix(release): restore Core compatibility and unlock ordering

Home 2 now carries forward the Core 1.7 compatibility protections from the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1013,7 +1013,7 @@ static String getQdnBridgeTag(String bridgeToken, boolean homeV2Bridge) {
"if(!window.parent||window.parent===window){reject(new Error('QDN app bridge is unavailable.'));return;}" +
"var requestId=String(Date.now())+'-'+String(++nextRequestId);" +
"var action=request&&typeof request==='object'?String(request.action||'').toUpperCase():'';" +
"var longActions={PUBLISH_MULTIPLE_QDN_RESOURCES:1,PUBLISH_QDN_RESOURCE:1,PREVIEW_QDN_PUBLISH_SOURCE:1,DELETE_QDN_RESOURCE:1,APPROVE_GROUP_JOIN_REQUEST:1,INVITE_TO_GROUP:1,JOIN_GROUP:1,LEAVE_GROUP:1,UPDATE_GROUP:1,BUY_NAME:1,CANCEL_SELL_NAME:1,REGISTER_NAME:1,SELL_NAME:1,UPDATE_NAME:1,SEND_CHAT_MESSAGE:1,CREATE_POLL:1,VOTE_ON_POLL:1,UPDATE_POLL:1,SHOW_NOTIFICATION:1,NOTIFICATION_ADD:1,GET_APP_ASSIGNMENTS:1,REQUEST_APP_ASSIGNMENT:1,BOOKMARKS_GET:1,BOOKMARKS_APPLY:1,BOOKMARKS_OPEN:1,NOTIFICATION_MANAGER_GET:1,NOTIFICATION_MANAGER_SET_MUTED:1,NOTIFICATION_MANAGER_REMOVE_RULES:1,NOTIFICATION_MANAGER_REVOKE:1,UNLOCK_SELECTED_ACCOUNT:1,GET_USER_WALLET:1,GET_WALLET_BALANCE:1,GET_USER_WALLET_INFO:1,GET_USER_WALLET_TRANSACTIONS:1,SEND_COIN:1,SET_CURRENT_FOREIGN_SERVER:1,GET_PRIVATE_DIRECT_ACTIVE_CHATS:1,GET_PRIVATE_GROUP_ACTIVE_CHATS:1,SEARCH_PRIVATE_DIRECT_CHAT_MESSAGES:1,SEARCH_PRIVATE_GROUP_CHAT_MESSAGES:1};" +
"var longActions={PUBLISH_MULTIPLE_QDN_RESOURCES:1,PUBLISH_QDN_RESOURCE:1,PUBLISH_CHAT_ATTACHMENT:1,PREVIEW_QDN_PUBLISH_SOURCE:1,DELETE_QDN_RESOURCE:1,APPROVE_GROUP_JOIN_REQUEST:1,INVITE_TO_GROUP:1,JOIN_GROUP:1,LEAVE_GROUP:1,UPDATE_GROUP:1,BUY_NAME:1,CANCEL_SELL_NAME:1,REGISTER_NAME:1,SELL_NAME:1,UPDATE_NAME:1,SEND_CHAT_MESSAGE:1,SEND_CHAT_EDIT:1,SEND_CHAT_DELETE:1,SEND_CHAT_REACTION:1,SEND_DIRECT_CHAT_MESSAGE:1,SEND_DIRECT_CHAT_EDIT:1,SEND_DIRECT_CHAT_DELETE:1,SEND_DIRECT_CHAT_REACTION:1,REQUEST_PRIVATE_GROUP_CHAT_KEY:1,RESOLVE_PRIVATE_GROUP_CHAT_KEY_REQUESTS:1,ROTATE_PRIVATE_GROUP_CHAT_KEY:1,SEND_PRIVATE_GROUP_CHAT_MESSAGE:1,SEND_PRIVATE_GROUP_CHAT_EDIT:1,SEND_PRIVATE_GROUP_CHAT_DELETE:1,SEND_PRIVATE_GROUP_CHAT_REACTION:1,CREATE_POLL:1,VOTE_ON_POLL:1,UPDATE_POLL:1,SHOW_NOTIFICATION:1,NOTIFICATION_ADD:1,GET_APP_ASSIGNMENTS:1,REQUEST_APP_ASSIGNMENT:1,BOOKMARKS_GET:1,BOOKMARKS_APPLY:1,BOOKMARKS_OPEN:1,NOTIFICATION_MANAGER_GET:1,NOTIFICATION_MANAGER_SET_MUTED:1,NOTIFICATION_MANAGER_REMOVE_RULES:1,NOTIFICATION_MANAGER_REVOKE:1,UNLOCK_SELECTED_ACCOUNT:1,GET_USER_WALLET:1,GET_WALLET_BALANCE:1,GET_USER_WALLET_INFO:1,GET_USER_WALLET_TRANSACTIONS:1,SEND_COIN:1,SET_CURRENT_FOREIGN_SERVER:1,GET_PRIVATE_DIRECT_ACTIVE_CHATS:1,GET_PRIVATE_GROUP_ACTIVE_CHATS:1,SEARCH_PRIVATE_DIRECT_CHAT_MESSAGES:1,SEARCH_PRIVATE_GROUP_CHAT_MESSAGES:1};" +
"var timeoutMs=longActions[action]?330000:30000;" +
"var timeoutId=setTimeout(function(){delete pending[requestId];reject(new Error('QDN app request timed out.'));},timeoutMs);" +
"pending[requestId]={resolve:resolve,reject:reject,timeoutId:timeoutId};" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,25 @@ public void homeV2BridgeAddsSeparateQortalRequestWithoutChangingProductionBridge
assertTrue(homeV2.contains("qortiumBridgeStateChanged"));
assertTrue(homeV2.contains("data.bridgeToken!==bridgeToken"));
assertTrue(homeV2.contains("Object.keys(data.error)"));
for (String action : Arrays.asList(
"SEND_CHAT_MESSAGE",
"SEND_CHAT_EDIT",
"SEND_CHAT_DELETE",
"SEND_CHAT_REACTION",
"SEND_DIRECT_CHAT_MESSAGE",
"SEND_DIRECT_CHAT_EDIT",
"SEND_DIRECT_CHAT_DELETE",
"SEND_DIRECT_CHAT_REACTION",
"REQUEST_PRIVATE_GROUP_CHAT_KEY",
"RESOLVE_PRIVATE_GROUP_CHAT_KEY_REQUESTS",
"ROTATE_PRIVATE_GROUP_CHAT_KEY",
"SEND_PRIVATE_GROUP_CHAT_MESSAGE",
"SEND_PRIVATE_GROUP_CHAT_EDIT",
"SEND_PRIVATE_GROUP_CHAT_DELETE",
"SEND_PRIVATE_GROUP_CHAT_REACTION"
)) {
assertTrue(action + " must use the long Android bridge timeout", homeV2.contains(action + ":1"));
}
}

@Test
Expand Down
10 changes: 10 additions & 0 deletions docs/BRIDGE_ACTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@ key, native path, or attachment bytes enter the journal. See
[Home 2 Chat operational completion](HOME_V2_OPERATIONAL_COMPLETION.md) for the
lifecycle, retention, duplicate-prevention, and platform/route matrix.

Qortium private-group sends are the narrow exception to the same-target block:
when no current-epoch key exists, Home first creates and broadcasts an
independent `KEY_ANNOUNCEMENT`, then submits the requested message mutation only
after that broadcast is accepted. If the announcement outcome is unknown, the
journal entry carries `stage: "key-announcement"`, the result carries
`messageSubmitted: false`, and another attempt of the original message remains
safe because no message transaction was built or submitted. The announcement
signature is still retained until the app observes a usable current-epoch key
and explicitly forgets it.

The Home-data manager actions are `BOOKMARKS_HAS_PERMISSION`, `BOOKMARKS_GET`,
`BOOKMARKS_APPLY`, `BOOKMARKS_OPEN`, `NOTIFICATION_MANAGER_HAS_PERMISSION`,
`NOTIFICATION_MANAGER_GET`, `NOTIFICATION_MANAGER_SET_MUTED`,
Expand Down
5 changes: 4 additions & 1 deletion docs/HOME_CHAT_PORTABILITY_ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,10 @@ Home consumes Core C0-C4 and owns the portable lifecycle:
- read atomic group state and fail closed above 39 members or when a public key
is missing;
- recover recipient-wrapped keys, encrypt/decrypt messages, create/relay key
requests and announcements, and rotate on membership changes; and
requests and announcements, and rotate on membership changes;
- automatically create and announce a missing current-epoch key on the first
message mutation, then continue that same approved mutation without exposing
key-management controls to the app or user; and
- build, attest, PoW, sign, and broadcast every control/message through the
selected local/custom/public route.

Expand Down
4 changes: 2 additions & 2 deletions docs/HOME_V2_BRIDGE_COMPATIBILITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pages and Home 1.x retain Core's injected bridge client.
| `SHOW_ACTIONS` | both | Protocol-, route-, and platform-specific callable string array | No prompt; disabled or platform-impossible routes remove node-dependent actions, while a temporary outage keeps implemented actions discoverable | yes | yes |
| `WHICH_UI` | both | Host identifier string | No prompt | yes | yes |
| `GET_HOST_INFO` | both | Host/platform metadata plus authoritative protocol, network, configured/effective route, availability, reachability, and opaque route revision | No prompt | yes | yes |
| `GET_PENDING_TRANSACTIONS` | both | This app/account/chain's opaque unknown-outcome entries without Home-internal account or app keys | Route-independent scoped `transactions.pending.read` approval; message and key material are never stored | yes | yes |
| `GET_PENDING_TRANSACTIONS` | both | This app/account/chain's opaque unknown-outcome entries without Home-internal account or app keys; an automatic QPGC setup entry may include `stage: "key-announcement"` | Route-independent scoped `transactions.pending.read` approval; message and key material are never stored | yes | yes |
| `FORGET_PENDING_TRANSACTION` | both | `{ forgotten, network, signature }` | Route-independent single-request `transactions.pending.forget` approval after app reconciliation | yes | yes |
| `GET_NODE_INFO`, `GET_NODE_STATUS` | both | Bare Core JSON | Protocol selects Qortium or Qortal | yes | yes |
| `IS_USING_PUBLIC_NODE` | both | Boolean for the configured route | Protocol selects network; remains callable while the route is unavailable | yes | yes |
Expand Down Expand Up @@ -69,7 +69,7 @@ pages and Home 1.x retain Core's injected bridge client.
| `SEND_DIRECT_CHAT_MESSAGE`, `SEND_DIRECT_CHAT_EDIT`, `SEND_DIRECT_CHAT_DELETE`, `SEND_DIRECT_CHAT_REACTION` | both | `{ signature, timestamp }`, or a signed non-retryable unknown-outcome result | Single-request direct-write prompt; recipient key, exact original/reference/participant binding, payload codec, route, account, and app/tab context rechecked before signing. Qortium uses QDM1; Qortal uses legacy v2 secretbox. Delete clears displayed content but does not erase transactions | yes | yes |
| `GET_PRIVATE_GROUP_ACTIVE_CHATS`, `GET_PRIVATE_GROUP_CHAT_STATE`, `SEARCH_PRIVATE_GROUP_CHAT_MESSAGES` | both | Selected-account-scoped state or retained rows with plaintext `data`; unavailable rows report `MISSING_KEY` without ciphertext or reusable keys | Qortium verifies bounded signed QPGC state/control records. Qortal accepts only newest-valid current-admin `DOCUMENT_PRIVATE` bundles and old/new authenticated `encryptSingle` messages. Both recheck current membership, account, app/tab, and route | yes | yes |
| `REQUEST_PRIVATE_GROUP_CHAT_KEY`, `RESOLVE_PRIVATE_GROUP_CHAT_KEY_REQUESTS`, `ROTATE_PRIVATE_GROUP_CHAT_KEY` | both | Recovery state, `{ signature, timestamp }`, bounded relay/republish result, or signed non-retryable unknown-outcome result | Single-request prompt. Qortium creates/relays QPGC controls; Qortal recovers from or republishes/rotates the current-admin key bundle. Qortal publication requires the selected operator's QDN staging route or returns `NODE_CAPABILITY_MISSING` | yes | yes |
| `SEND_PRIVATE_GROUP_CHAT_MESSAGE`, `SEND_PRIVATE_GROUP_CHAT_EDIT`, `SEND_PRIVATE_GROUP_CHAT_DELETE`, `SEND_PRIVATE_GROUP_CHAT_REACTION` | both | `{ signature, timestamp }`, or signed non-retryable unknown-outcome result | Qortium uses QPGC v1 (maximum 39 members). Qortal uses Hub-compatible app-level secretbox with a 2,225-byte plaintext ceiling that fits both retained encodings and reaction type 102. Home rechecks membership, key state, reference, sender ownership where required, route, account, and app/tab context before signing | yes | yes |
| `SEND_PRIVATE_GROUP_CHAT_MESSAGE`, `SEND_PRIVATE_GROUP_CHAT_EDIT`, `SEND_PRIVATE_GROUP_CHAT_DELETE`, `SEND_PRIVATE_GROUP_CHAT_REACTION` | both | `{ signature, timestamp }`, or signed unknown-outcome result; an uncertain automatic QPGC setup additionally returns `stage: "key-announcement"` and `messageSubmitted: false` | Qortium uses QPGC v1 (maximum 39 members) and automatically creates, wraps, announces, stores, and immediately uses a missing current-epoch key before continuing the requested mutation. Qortal keeps the Hub-compatible app-level secretbox/manual bundle lifecycle with a 2,225-byte plaintext ceiling. Home rechecks membership, key state, reference, sender ownership where required, route, account, and app/tab context before signing | yes | yes |
| `PUBLISH_CHAT_ATTACHMENT`, `GET_CHAT_ATTACHMENT_STREAM_URL`, `OPEN_CHAT_ATTACHMENT_VIEWER`, `SAVE_CHAT_ATTACHMENT` | both | Immutable encrypted descriptor, expiring plaintext stream URL, viewer result, or save result | Home-issued source token only; one-request `chat.attachment` approval; QATT/QENC v2 for Qortium, distinct marked Qortal direct/generic-group formats, and Hub-compatible Qortal private-group images; 1 MiB ciphertext ceiling; exact hash/size, peer/membership, account, app/tab, chain, and route checks; no inline plaintext or reusable key crosses into the app, while the approved stream URL grants temporary bounded byte access | yes | yes |
| `GET_GROUP`, `GET_ACCOUNT_GROUPS`, `GET_GROUP_MEMBERS`, `GET_GROUP_JOIN_REQUESTS`, `GET_ACCOUNT_GROUP_JOIN_REQUESTS`, `GET_ADMIN_GROUP_JOIN_REQUESTS`, `GET_ACTIVE_CHATS` | both | Bare Core JSON | No prompt; bounded anonymous public reads; positive-integer `groupId`, address regex, strict booleans, 100-entry page cap where Core has none | yes | yes |
| `SEARCH_GROUPS` | `qdnRequest` | Bare Core JSON array | Qortium-only — `/groups/search` does not exist on Qortal (verified absent from the Qortal master 6.1.5 and develop checkouts' `GroupsResource.java`); required non-negative-length `query`, `visibility` validated against Core's real `ALL`/`OPEN`/`CLOSED` enum (not Hub's `PUBLIC`/`PRIVATE` terminology), strict `prefixOnly`, 100-entry page cap | yes | yes |
Expand Down
11 changes: 11 additions & 0 deletions docs/HOME_V2_OPERATIONAL_COMPLETION.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ The journal contains only:
- bridge protocol and authoritative chain;
- exact mutation action;
- signed transaction signature and timestamp;
- the optional `key-announcement` stage when an automatic QPGC bootstrap is
uncertain and the user message is known not to have been submitted;
- creation time; and
- a normalized group, direct-address, public-resource, or operation target.

Expand Down Expand Up @@ -72,6 +74,15 @@ selected chain for that signature, update its delivery state, and then forget
the entry. Forgetting is explicit because absence from one node's retained
window is not proof that a signed transaction was never accepted.

An automatic Qortium private-group bootstrap is a bounded exception to the
same-target retry block. Home broadcasts the key announcement before building
the user mutation. If that control outcome is unknown, the retained entry is
marked `stage: "key-announcement"` and the bridge result proves
`messageSubmitted: false`. Retrying the original mutation is therefore safe:
it either discovers the retained key or announces another key, and QPGC
messages bind their exact key ID. The app forgets the setup entry once a usable
current-epoch key is observed.

## Route and platform matrix

Automated contract tests require every public-group, private-group, direct,
Expand Down
Loading
Loading