3 Commits
Author SHA1 Message Date
MarekWoandClaude Opus 5.5 593695091d fix(map): request the referrer on OSM tiles so a proxy's Referrer-Policy can't block them
OpenStreetMap answers referer-less tile requests with a 403 'Access blocked'
image. A reverse proxy adding Referrer-Policy: no-referrer (seen with Traefik)
made every map in the app show that instead of tiles. Setting referrerPolicy
on the tile images overrides the document-level policy.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-02 21:07:56 +02:00
MarekWoandClaude Opus 5 59ef0dd6be fix(ui): explain a non-JSON answer instead of printing its parse error
An instance published behind an access list or Cloudflare Zero Trust gets
an HTML login or error page in place of JSON once that session expires.
`resp.json()` then throws "Unexpected token '<', "<!DOCTYPE "...", and the
repeater panes printed that at the user. Diagnosed on the production
server 2026-09-08: the origin had answered a clean 504 JSON ("No reply
from repeater within 10s") and nginx passed it through untouched, so the
HTML could only have come from in front of it.

New window.fetchJson() (fetch-utils.js, loaded from _head_i18n.html so all
eight entry points get it) returns the parsed body — `data.success` and
`data.error` checks are unchanged — and reports anything else in the same
shape the API uses: {success:false, error, http_status, non_json|offline}.
A 401/403 with a non-JSON body is named as an expired login, since that is
the case the user can act on; an error the API sent as JSON keeps its own
message. 88 call sites converted; those that deliberately inspect the
response (resp.ok/status, blob, uploads) still use fetch directly.

Also stops three catch blocks in the repeater panes from rendering the raw
exception text.

Verified in a browser: normal calls unchanged, an HTML answer comes back
as a readable sentence, a refused connection as a network error, and all
seven pages load with no console errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-08 11:47:31 +02:00
MarekWoandClaude Opus 5 e4a98a427e feat(chat): add the share composer — "+" button, pickers and live preview
Completes the sharing feature: you can now send what the cards render.

A "+" button next to the emoji button opens a three-item menu — contact,
channel, location. Five actions in the official app collapse into three
because "me" and "my position" are not separate destinations, just the
first, pinned entry of the picker they belong to. Same number of taps,
one less menu level, and every entry behaves identically.

The payload is inserted into the input as plain text, byte-identical to
what the official app sends. A protected "chip" was considered and
dropped: it is unknown whether the official app renders a card when the
token is not the whole message body, and that risk is not worth the
cosmetics. The reassurance went into a live preview above the composer
instead, rendered through the very same processMessageContent() the
recipient will use — so it cannot drift. Edit the token by hand and the
preview vanishes, which is immediate feedback that the message will no
longer render as a card.

Details worth knowing:

- The contact picker lists device memory and cache together, since the
  cache is where adverts land and those are the contacts worth passing
  on. It renders at most 150 rows: this mesh has over 1500 cached
  contacts, and the full list costs thousands of DOM nodes for something
  nobody scrolls to the end of. Search runs over the whole set.
- The channel picker carries the region scope along, because a recipient
  transmitting under the firmware default would not match the sender.
- The location picker holds both cases in one dialog: our own position as
  a button, plus a Leaflet map for any other point.
- Byte budgets are enforced up front (135 channel / 150 DM). A token that
  cannot fit is refused rather than truncated — shortening a public key
  or a channel secret destroys it.
- The DM composer starts disabled until a conversation is chosen, so the
  button mirrors the input's disabled state via a MutationObserver.

Also fixes _selfInfo being populated only inside the map modal's shown
handler, which left it null on a fresh page — the location cards need our
own position at render time for distance and bearing.

Verified in a real browser against the live device: 32 assertions on the
channel composer, 13 on the DM panel (including its local map modal),
both suites green with no console errors.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 11:12:58 +02:00