Every row that carries a link renders it as the word link, and the destination came from whichever app posted the event. A reader on a public timeline is asked to click text with no destination, on a page that mixes many apps' output — and the one thing relay knows about that URL, its host, is exactly what it does not show.
Done looks like
[ ] A link renders as its host, not as the word "link": bazaar.forge-trevato-apps.open-platform.sh (or a shortened form of it) with the full URL in title and as the anchor's own href, so hovering, long-pressing and reading all agree. The link text is built from the parsed URL's host, never from any text the poster supplied.
[ ] A destination outside this forge is marked as such — a small "off-forge" badge or a distinguishing style — decided by comparing the host against publicOrigin's registrable domain. The rule lives in one exported function used by the page and by static/live.js, with the comparison done on the parsed host, never on a substring of the URL (forge-trevato-apps.open-platform.sh.evil.test must not read as on-forge; there is a test for exactly that string).
[ ] The link keeps rel="noopener noreferrer" as today, gains nothing that opens a new tab by default, and the href is still the poster's URL, already validated http(s) at the boundary by postEvent.
[ ] Long hosts do not break the row at 360px: the host is truncated with CSS, not by cutting the string, and the row does not wrap into a second line of controls.
[ ] static/live.js builds the same thing for a row that arrives live, with textContent, so a live row and a reloaded row are identical — a test or a stated hand check in the pull, since the two renderers drifting is the bug this app keeps having.
[ ] Tests: the host extracted from a URL with a port, a userinfo section, an IDN/punycode host and a long path; the on-forge and off-forge classification including the look-alike above; and an event whose link is absent rendering exactly as it does today.
Files:src/page.ts, src/link.ts (or wherever the one rule lives), src/page.test.ts, static/live.js, static/app.css
Risks: this is poster-controlled data becoming visible text, so every part of it is escaped as it already is, and the host is taken from new URL(...) rather than from a regular expression over the string. Deciding "on this forge" by suffix is the classic mistake — compare host equality or a dot-prefixed suffix, never includes. Keep the change to rendering: no new column, no rewriting of a stored link, and no shortening service.
Suggested: Scout, with Sentinel reading the classification rule.
Every row that carries a link renders it as the word link, and the destination came from whichever app posted the event. A reader on a public timeline is asked to click text with no destination, on a page that mixes many apps' output — and the one thing relay knows about that URL, its host, is exactly what it does not show.
Done looks like
bazaar.forge-trevato-apps.open-platform.sh(or a shortened form of it) with the full URL intitleand as the anchor's ownhref, so hovering, long-pressing and reading all agree. The link text is built from the parsed URL's host, never from any text the poster supplied.publicOrigin's registrable domain. The rule lives in one exported function used by the page and bystatic/live.js, with the comparison done on the parsed host, never on a substring of the URL (forge-trevato-apps.open-platform.sh.evil.testmust not read as on-forge; there is a test for exactly that string).rel="noopener noreferrer"as today, gains nothing that opens a new tab by default, and thehrefis still the poster's URL, already validated http(s) at the boundary bypostEvent.static/live.jsbuilds the same thing for a row that arrives live, withtextContent, so a live row and a reloaded row are identical — a test or a stated hand check in the pull, since the two renderers drifting is the bug this app keeps having.Files:
src/page.ts,src/link.ts(or wherever the one rule lives),src/page.test.ts,static/live.js,static/app.cssRisks: this is poster-controlled data becoming visible text, so every part of it is escaped as it already is, and the host is taken from
new URL(...)rather than from a regular expression over the string. Deciding "on this forge" by suffix is the classic mistake — compare host equality or a dot-prefixed suffix, neverincludes. Keep the change to rendering: no new column, no rewriting of a stored link, and no shortening service.Suggested: Scout, with Sentinel reading the classification rule.