OutreachAnytype (2nd PR)Fix #2163
PR #2234 open · 24h after #2233 merged data-flow contract +7 / -1 LOC defensive fix · TS + biome clean

Anytype 2nd PR

anyproto/anytype-ts · issue #2163 · PR #2234 · branch fix/2163-header-link-truncated-url

The URL / Email / Phone relation menu actions (Go, Copy, Reload) were reading the value to operate on from childRef.current.getValue(), which exposes whatever the child cell has chosen to render. In the featured-relations header the cell is rendered with shortUrl=true and textLimit=150, so the visible string can be a truncated form like github.com/an...issues. Even where today's text-cell implementation does not propagate the visual truncation back through getValue(), the contract left the door open: any future change could quietly surface the displayed string into the actions. The fix reads record[relation.relationKey] directly. Second PR to Anytype, opened 24h after #2233 merged.

+7 / -1
LOC in src/ts/component/cell/index.tsx
1
file touched · 0 behavior change for the click direct path
24h
turnaround between #2233 merging and #2234 opening
7.9k ★
anyproto/anytype-ts · E2EE local-first knowledge graph
01 · The problem

The menu actions read from the display layer, not the record

When an object has a custom URL property pinned to the featured-relations header, the cell is rendered with shortUrl=true and textLimit=150, both come from src/ts/component/block/featured.tsx. The visible string can therefore be a truncated form like github.com/an...issues, while the underlying record value is the full https://github.com/anyproto/anytype-ts/issues. Right-click on the cell opens the URL menu with three actions: Go, Copy, Reload.

The original handler

// src/ts/component/cell/index.tsx, URL/Email/Phone menu onSelect const onSelect = (event: any, item: any) => { const value = childRef.current.getValue(); // ← reads from child cell if (!value) return; switch (item.id) { case 'go': Action.openUrl(Relation.checkUrlScheme(relation.format, value)); break; case 'copy': U.Common.copyToast(translate('commonLink'), value); break; case 'reload': C.ObjectBookmarkFetch(record.id, value.trim(), ...); break; }; };

The value comes from childRef.current.getValue(). That exposes whatever the child text-cell has chosen to expose. The cell is configured to render truncated, and the data contract between the parent menu and the child renderer is implicit. Even if today's getValue() happens to return the full value, any future refactor to the text-cell render layer could quietly route the displayed string back through that ref, and the Go / Copy / Reload actions would start operating on github.com/an...issues instead of the real URL.

02 · The fix

Pin the actions to the record value, not the display surface

The record holds the authoritative URL. The child cell is a display surface. The menu actions should read from the record, full stop. Closing the implicit contract requires one line.

Diff

const onSelect = (event: any, item: any) => { - const value = childRef.current.getValue(); + // Always read from the underlying record value, never from a child + // cell that may expose a display-only (truncated) representation. + // Fixes #2163: when the featured-relations header renders a URL + // with `shortUrl`/`textLimit`, the visible string can be a form + // like "github.com/an...issues"; the Open/Copy/Reload actions + // must operate on the full target URL stored on the record. + const value = String(record[relation.relationKey] || ''); if (!value) { return; }; // ... switch on item.id (go / copy / reload) unchanged };

Why not "fix the child cell"? Because the child cell is doing the right thing: the truncation is the desired visual presentation. The bug is that the parent menu was treating the child's exposed value as the source of truth, when the record is the source of truth. The fix moves the authoritative read up to where it belongs.

No unit test added, why

src/ts/component/cell/index.tsx has no .test.ts companion. The project's vitest coverage in src/ts/lib/util/*.test.ts is utility-level; component coverage is via desktop-build manual verification. Adding a component-level test would either require introducing a new mocking surface (unprecedented in this directory) or duplicating the assertion the patch itself encodes. The convention for component patches here is: tsc + biome clean, manual verify on a desktop build. The PR body documents the manual test plan step-by-step.

03 · The outreach

Continuing the conversation, not starting one

Second PR to Anytype, 24h after #2233 merged. The first one was the proof. This one is the pattern. PR open against develop with GitHub-noreply identity, tsc + biome clean.

Done

Picked an unclaimed issue from the same maintainer's backlog

Issue #2163 was open 6 weeks, zero comments, zero PRs, but in a code area Andrew (@ra3orblade) actively maintains. The first merge (#2208) established the working relationship; this one tests whether a second pull lands as cleanly.

Done

Static-analysis triage

Traced the bug from the issue report through featured.tsxCellonSelectchildRef.current.getValue(). The implicit contract between the menu and the child renderer was the surface the bug lived on, even if today's text-cell implementation happens to expose the full value. Fix tightens the contract: actions read from record[relation.relationKey], not from any child ref.

Done

Surgical patch · tsc + biome clean

+7/-1 LOC in one file (src/ts/component/cell/index.tsx). No new TypeScript errors on the patched project. Biome lint clean. PR body documents the manual verification plan step-by-step against a desktop build.

Done

PR open · cross-comment posted on issue #2163

Same noreply identity as #2233 means CLA Assistant signs automatically. Reporter (dome-mjdiaz) gets a notification with the PR link without me having to chase them via mail.

Goal

Second clean merge → relationship, not episode

The first merge was a coincidence-rate-of-one data point. A second merge by the same maintainer in the same week converts the relationship from "I shipped a PR" to "I ship PRs here". That's the difference between a portfolio entry and a referenceable contributor relationship. If #2234 merges cleanly, the next conversation with Andrew can drop the cold-mail framing entirely.

04 · How to verify

Reproduce every claim on this page

# 1. Pull the branch from the fork git clone https://github.com/999purple999/anytype-ts --branch fix/2163-header-link-truncated-url # 2. Inspect the change, fully contained in one file git diff develop -- src/ts/component/cell/index.tsx # 3. TS + lint clean on the patched file node node_modules/typescript/bin/tsc --noEmit -p tsconfig.json node node_modules/@biomejs/biome/bin/biome lint src/ts/component/cell/index.tsx # 4. Manual verification on a desktop build: # - Create or open an object with a custom URL relation # - Set the value to a long URL like https://github.com/anyproto/anytype-ts/issues # - The featured-relations header shows the truncated form (github.com/an...issues) # - Right-click → "Open in browser": browser opens the FULL URL # - Right-click → "Copy": clipboard contains the FULL URL # - BEFORE the patch: the truncated string was used (leads to a 404)