Skip to content

feat(android): tap-to-expand link previews in chat transcript#100898

Merged
steipete merged 1 commit into
mainfrom
feat/android-link-previews-nash
Jul 6, 2026
Merged

feat(android): tap-to-expand link previews in chat transcript#100898
steipete merged 1 commit into
mainfrom
feat/android-link-previews-nash

Conversation

@steipete

@steipete steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Related: #100699

What Problem This Solves

Bare URLs in the Android chat transcript render as plain tappable links with no way to see what's behind them before leaving the app.

Why This Change Was Made

Adds tap-to-expand link previews: a message whose text contains a safe web link shows a compact chip (domain + expand affordance) under the body; tapping it fetches OpenGraph metadata and expands into a card (title + description), and tapping the card opens the URL exactly like tapping the link. Privacy is the design driver: no ambient fetches, ever — network activity happens only on an explicit user tap (see #22060 for why automatic preview fetches on message content are an injection/exfiltration hazard). The preview fetch is strictly less capable than tapping the link itself: same URL, same user gesture, display-only result — fetched with a locked-down client (no cookies, no auth/proxy authenticators, no auto-redirects; each of ≤3 manual redirect hops re-validated http/https; 6s total deadline; 512KB body cap; text/html only). Metadata is untrusted plain text: HTML entities decoded, control characters stripped, whitespace collapsed, length-capped, rendered as plain Text. Results (including failures) sit in a 64-entry in-memory LRU — nothing persists to disk. The card is text-only for now: the Android transcript has no remote-image facility and this PR deliberately adds no dependency for one (og:image is parsed and safe-resolved for when one exists).

User Impact

Android users can peek at a link's title and description before opening it, with zero background network traffic from message content. Failed lookups show a plain "No preview available" state.

Evidence

  • Focused unit suite ChatLinkPreviewTest green (12 tests): OG parsing (full set, <title> fallback, attribute-order tolerance, relative og:image resolution, control-char stripping and caps), first-URL extraction (bare URLs, markdown links, code-fence/inline-code exclusion, unsafe schemes skipped), MockWebServer fetcher (redirect chain, >3 hops rejected, non-HTML rejected, body cap, timeout), cache hit avoids refetch (request-count assertion).
  • OkHttp is the app's existing HTTP client (libs.okhttp, used by the gateway session) — no new dependency.
  • ktlint clean on changed files; corepack pnpm native:i18n:sync run for the new strings.
  • Structured review (codex) — no actionable findings; a private-network (SSRF-style) block was consciously not added since the preview can only fetch what tapping the link already reaches, and the result never leaves the device.

@openclaw-barnacle openclaw-barnacle Bot added app: android App: android size: L maintainer Maintainer-authored PR labels Jul 6, 2026
@steipete
steipete force-pushed the feat/android-link-previews-nash branch from fc2239f to 989df73 Compare July 6, 2026 13:17

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: fc2239fb8f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

val title = sanitizeMetadataText(ogTitle ?: findTitle(html), LINK_PREVIEW_TITLE_MAX_CHARS)
val description = sanitizeMetadataText(ogDescription, LINK_PREVIEW_DESCRIPTION_MAX_CHARS)
val imageUrl = resolveSafeWebUrl(baseUrl, decodeHtmlEntities(ogImage.orEmpty()))
if (title == null && description == null && imageUrl == null) return LinkPreviewResult.Failed

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Treat image-only metadata as unavailable until it is rendered

When a page only exposes og:image but no title or description, this condition returns Loaded because imageUrl is non-null. The card UI only renders title and description, so those expanded previews show just the domain and never display the existing “No preview available” state; either ignore image-only metadata for now or render the image before counting it as a successful preview.

Useful? React with 👍 / 👎.

@clawsweeper

clawsweeper Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Codex review: needs real behavior proof before merge. Reviewed July 6, 2026, 9:40 AM ET / 13:40 UTC.

Summary
Adds an Android chat link-preview parser/fetcher, compact and expanded Compose preview UI, i18n inventory updates, and unit coverage for parsing, URL extraction, fetch limits, redirects, DNS policy, and caching.

PR surface: Other +915. Total +915 across 6 files.

Reproducibility: yes. from source for the review finding: parseOpenGraph returns Loaded for image-only metadata, while the expanded UI renders only title and description. I did not run an Android UI reproduction in this read-only review.

Review metrics: 1 noteworthy metric.

  • New chat URL fetch surface: 1 Android fetcher added. Chat content can now trigger network metadata requests after a user tap, so privacy and security proof matters before merge.

Stored data model
Persistent data-model change detected: vector/embedding metadata: apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatLinkPreview.kt. Confirm migration or upgrade compatibility proof before merge.

Merge readiness
Overall: 🦪 silver shellfish
Proof: 🦪 silver shellfish
Patch quality: 🦐 gold shrimp
Result: blocked until real behavior proof from a real setup is added.

Overall follows the weaker of proof and patch quality, so missing proof can cap an otherwise strong patch.

Rank-up moves:

  • [P1] Fix image-only OpenGraph handling so it either shows an image or falls back to the unavailable state.
  • [P1] Add redacted real Android proof, preferably a screenshot or short recording of the chip expanding into a preview card after an explicit tap.

Proof guidance:

  • [P1] Needs real behavior proof before merge: The PR body provides unit-test, ktlint, i18n, and CI-style evidence only; it does not include a real Android screenshot, recording, terminal output, linked artifact, or redacted runtime log showing the tap-to-expand flow after the change. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Risk before merge

  • [P1] This adds a security-sensitive client-side URL metadata fetch path from chat content; source and tests show mitigations, but real Android proof of the user-gesture path is still missing.
  • [P1] The PR body's security note says a private-network block was not added, while current head now includes public-only host/DNS checks, so the merge rationale should be refreshed before maintainer review.

Maintainer options:

  1. Fix and prove the gesture path (recommended)
    Treat image-only metadata as unavailable or render images, refresh the stale security rationale, and add redacted Android proof before merge.
  2. Accept tests-only network proof
    A maintainer could intentionally accept the unit/CI evidence for the fetch policy, but that leaves the user-visible Android flow unproven.
  3. Pause this feature slice
    If the preview-fetch surface is not ready for review, pause this PR and keep the broader mobile polish issue as the tracking item.

Next step before merge

  • [P1] The code blocker is narrow, but external real behavior proof is missing and the PR is maintainer-labeled, so this should stay in contributor/maintainer review rather than ClawSweeper repair automation.

Security
Cleared: No concrete security or supply-chain defect found in the diff; the new fetch path is gesture-gated and source inspection shows no cookies, no auth/proxy authenticators, manual redirects, public-only DNS, a body cap, and no persistence.

Review findings

  • [P2] Treat image-only metadata as unavailable — apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatLinkPreview.kt:94
Review details

Best possible solution:

Land this only after image-only metadata either fails with the existing unavailable state or is visibly rendered, and after a redacted Android run or screenshot shows tap-to-expand behavior with no ambient fetch.

Do we have a high-confidence way to reproduce the issue?

Yes from source for the review finding: parseOpenGraph returns Loaded for image-only metadata, while the expanded UI renders only title and description. I did not run an Android UI reproduction in this read-only review.

Is this the best way to solve the issue?

No: the branch is close, but the maintainable fix is to fail image-only metadata until image rendering exists, or actually render the image. The PR also needs real Android behavior proof before merge.

Full review comments:

  • [P2] Treat image-only metadata as unavailable — apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatLinkPreview.kt:94
    When a page has only og:image, this condition returns Loaded because imageUrl is non-null. The expanded UI currently renders only title and description, so users get a domain-only card instead of the “No preview available” state; either fail image-only metadata until images are rendered, or render the image before counting it as loaded.
    Confidence: 0.92

Overall correctness: patch is incorrect
Overall confidence: 0.87

AGENTS.md: found and applied where relevant.

Codex review notes: model internal, reasoning high; reviewed against 88f1ec38d4a5.

Label changes

Label changes:

  • add P3: This is a mobile chat polish feature with limited blast radius, not a blocked setup path or broken core runtime.
  • add merge-risk: 🚨 security-boundary: The diff adds a new user-initiated URL metadata fetch path from chat content, which green CI alone does not fully prove safe in the Android app.
  • add rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • add status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body provides unit-test, ktlint, i18n, and CI-style evidence only; it does not include a real Android screenshot, recording, terminal output, linked artifact, or redacted runtime log showing the tap-to-expand flow after the change. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.

Label justifications:

  • P3: This is a mobile chat polish feature with limited blast radius, not a blocked setup path or broken core runtime.
  • merge-risk: 🚨 security-boundary: The diff adds a new user-initiated URL metadata fetch path from chat content, which green CI alone does not fully prove safe in the Android app.
  • rating: 🦪 silver shellfish: Overall readiness is 🦪 silver shellfish; proof is 🦪 silver shellfish and patch quality is 🦐 gold shrimp.
  • status: 📣 needs proof: The PR needs real behavior proof before ClawSweeper can clear the contributor ask. Needs real behavior proof before merge: The PR body provides unit-test, ktlint, i18n, and CI-style evidence only; it does not include a real Android screenshot, recording, terminal output, linked artifact, or redacted runtime log showing the tap-to-expand flow after the change. After adding proof, update the PR body; ClawSweeper should re-review automatically. If it does not, the PR author or someone with repository write access can comment @clawsweeper re-review.
Evidence reviewed

PR surface:

Other +915. Total +915 across 6 files.

View PR surface stats
Area Files Added Removed Net
Source 0 0 0 0
Tests 0 0 0 0
Docs 0 0 0 0
Config 0 0 0 0
Generated 0 0 0 0
Other 6 949 34 +915
Total 6 949 34 +915

What I checked:

  • Repository policy read: Root AGENTS.md and apps/android/AGENTS.md were read fully; root PR review guidance and Android dependency/license guidance were applicable, but the PR adds no new Android dependency. (AGENTS.md:1, 88f1ec38d4a5)
  • Live PR state: Live GitHub data shows this PR is open, cleanly mergeable, labeled maintainer, and has green Android build/test CI, but no screenshot, recording, runtime log, or other real behavior proof in the body or comments. (989df7362532)
  • Image-only metadata is counted as loaded: At the PR head, parseOpenGraph returns Failed only when title, description, and imageUrl are all null, so an og:image-only page becomes Loaded even though images are not rendered yet. (apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatLinkPreview.kt:94, 989df7362532)
  • Expanded card omits images: The expanded preview UI renders the domain plus optional title and description, with no image rendering path or fallback inside the Loaded branch. (apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt:237, 989df7362532)
  • Current main lacks this feature: Current main's Android chat bubble renders message parts and timestamps without any link-preview component, so the PR is not obsolete on main. (apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt:812, 88f1ec38d4a5)
  • Dependency contract checked: OkHttp 5.3.2 official source confirms the builder supports disabled redirects, no-cookie policy, no authenticators, explicit no-proxy configuration, and custom DNS; CommonMark 0.28.0 source confirms parser calls are thread-safe and the autolink extension emits normal Link nodes. (381493df8a6f)

Likely related people:

  • steipete: Authored this PR and the linked mobile polish issue, and recently authored/merged the Android chat actions PR that changed the same ChatScreen/ChatMessageViews surface. (role: recent area contributor and likely feature owner; confidence: high; commits: 989df7362532, 876ab9bb0b46; files: apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt, apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMessageViews.kt)
  • Vincent Koc: Git log/blame show commit 748169d introduced the current Android ChatScreen and ChatMarkdown renderer that this PR extends. (role: introduced adjacent chat renderer foundation; confidence: medium; commits: 748169d61a5c; files: apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatScreen.kt, apps/android/app/src/main/java/ai/openclaw/app/ui/chat/ChatMarkdown.kt)
What the crustacean ranks mean
  • 🦀 challenger crab: rare, exceptional readiness with strong proof, clean implementation, and convincing validation.
  • 🦞 diamond lobster: very strong readiness with only minor maintainer review expected.
  • 🐚 platinum hermit: good normal PR, likely mergeable with ordinary maintainer review.
  • 🦐 gold shrimp: useful signal, but proof or patch confidence is still limited.
  • 🦪 silver shellfish: thin signal; proof, validation, or implementation needs work.
  • 🧂 unranked krab: not merge-ready because proof is missing/unusable or there are serious correctness or safety concerns.
  • 🌊 off-meta tidepool: rating does not apply to this item.

Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics.

How this review workflow works
  • ClawSweeper keeps one durable marker-backed review comment per issue or PR.
  • Re-runs edit this comment so the latest verdict, findings, and automation markers stay together instead of adding duplicate bot comments.
  • A fresh review can be triggered by eligible @clawsweeper re-review comments, exact-item GitHub events, scheduled/background review runs, or manual workflow dispatch.
  • PR/issue authors and users with repository write access can comment @clawsweeper re-review or @clawsweeper re-run on an open PR or issue to request a fresh review only.
  • Maintainers can also comment @clawsweeper review to request a fresh review only.
  • Fresh-review commands do not start repair, autofix, rebase, CI repair, or automerge.
  • Maintainer-only repair and merge flows require explicit commands such as @clawsweeper autofix, @clawsweeper automerge, @clawsweeper fix ci, or @clawsweeper address review.
  • Maintainers can comment @clawsweeper explain to ask for more context, or @clawsweeper stop to stop active automation.

@clawsweeper clawsweeper Bot added rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. labels Jul 6, 2026
@steipete
steipete merged commit cd25de9 into main Jul 6, 2026
107 of 112 checks passed
@steipete
steipete deleted the feat/android-link-previews-nash branch July 6, 2026 14:02
@steipete

steipete commented Jul 6, 2026

Copy link
Copy Markdown
Contributor Author

Merged via squash.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

app: android App: android maintainer Maintainer-authored PR merge-risk: 🚨 security-boundary 🚨 May affect sandboxing, authorization, credentials, or sensitive data. P3 Low-priority cleanup, docs, polish, ergonomics, or speculative work. rating: 🦪 silver shellfish Thin PR readiness signal; proof, validation, or implementation needs work. size: L status: 📣 needs proof The PR needs real behavior proof before ClawSweeper can clear the contributor ask.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant