Implement a share API #93

Closed
opened 2026-08-18 00:41:45 +00:00 by Grandiras · 2 comments
Owner

Let users share cards, boards etc. easily. Hook into the share API wherever possible and fall back to a simple link copy. Make sure links go directly to the right spot, even if a redirect through the login screen is necessary. Also design Link cards for this with embedded content / a preview.

Let users share cards, boards etc. easily. Hook into the share API wherever possible and fall back to a simple link copy. Make sure links go directly to the right spot, even if a redirect through the login screen is necessary. Also design Link cards for this with embedded content / a preview.
Collaborator

🤖 On it — follow along in the action run; the reply will link the full session transcript.

📜 Live transcript — updating every ~5 minutes while the task runs.

🤖 On it — follow along in [the action run](https://git.grandiras.net/Grandiras/claude-bot/actions/runs/259); the reply will link the full session transcript. <!-- claude-run:comment --> 📜 [Live transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-93/run-259.md) — updating every ~5 minutes while the task runs.
Collaborator

Pushed to claude/issue-93.

What's there

Sharing. Services/ShareLink.cs is the one place that says what a shareable thing's link is and the one place that reads one back — a card's link is its board's plus ?card={id} (cards carry no board of their own, so there is no /card/{id} route to send anyone to), a board's is /board/{id}, a calendar's is /calendar/board/{id}. Two surfaces: a button in the card dialog's header, and a nav-menu row that hands over the board or calendar you are looking at — named explicitly, because a copied / means the reader's default board.

js/share-interop.js is a ladder: navigator.share where there is one, then the clipboard, then an off-screen selection. The button says "Share" either way and the toast says which happened; a dismissed sheet deliberately doesn't fall through to copying. URLs are resolved against location.origin in the browser rather than built absolutely on the server. calendar-interop.js now re-exports that clipboard fallback instead of keeping its own copy.

Links go to the right spot through the login screen. Services/ReturnUrl.cs is the single rule for whether a value may be a redirect target (local paths only — it ends up in a cookie the OAuth callback redirects to), applied by Login.razor and by /login/start, including on every one of that endpoint's error redirects. /dev/login/{did} takes the same parameter. BoardPage opens the named card and then strips the query by navigating to the current path — rebuilding it would be a route-parameter change, which the page reads as a switch of board and would close the dialog it just opened.

Link cards. A Ponente link alone on its line in a description or a comment renders as the thing it points at — title, board › column, an excerpt, tags, due date, comment count. RichText splits (fences and four-space indents excluded), RichTextView renders, LinkCard resolves. LinkPreviewService is the only card-reading path in the app that access-checks, because it's the only one whose id comes from text a member typed; missing and denied both fall back to the plain link, on purpose.

Open Graph tags describe the app and never the page — an unfurler is an anonymous fetch by somebody else's server and this board is private.

The card dialog: share button in the header, and a link to another card rendered as that card inside a comment

Verified

Built with .NET 10 (no new warnings), 273 unit tests pass including the new ShareLinkTests / RichTextTests / ReturnUrlTests, and I ran the app for real against Postgres to drive Playwright: the new share.spec.js (6 tests) plus board, filter, i18n, prefs, changelog, calendar, boards, mobile, entrance, icons, install, reconnect specs all pass. Four specs fail in this container — two in access.spec.js, requests.spec.js's full-circle test and one order-dependent calendar-all test — and I confirmed they fail identically on a stashed, pristine checkout: they need the PDS container and the DB-cleanup helpers, neither of which exists here.

One judgement call worth flagging: the issue's "embedded content / a preview" could also have meant putting card titles into the Open Graph tags so links unfurl richly in chat apps. I didn't, because that hands a private board's contents to whatever service the link passes through; the preview is rendered inside the app instead, against the reader's own access.


⏱ 34m 27s · 151 turns · tokens: 285.1k in / 88.1k out (+30139.7k cache-read)
📜 Full session transcript
→ Opened PR: #94

Pushed to **`claude/issue-93`**. ## What's there **Sharing.** `Services/ShareLink.cs` is the one place that says what a shareable thing's link is *and* the one place that reads one back — a card's link is its board's plus `?card={id}` (cards carry no board of their own, so there is no `/card/{id}` route to send anyone to), a board's is `/board/{id}`, a calendar's is `/calendar/board/{id}`. Two surfaces: a button in the card dialog's header, and a nav-menu row that hands over the board or calendar you are looking at — named explicitly, because a copied `/` means *the reader's* default board. `js/share-interop.js` is a ladder: `navigator.share` where there is one, then the clipboard, then an off-screen selection. The button says "Share" either way and the toast says which happened; a *dismissed* sheet deliberately doesn't fall through to copying. URLs are resolved against `location.origin` in the browser rather than built absolutely on the server. `calendar-interop.js` now re-exports that clipboard fallback instead of keeping its own copy. **Links go to the right spot through the login screen.** `Services/ReturnUrl.cs` is the single rule for whether a value may be a redirect target (local paths only — it ends up in a cookie the OAuth callback redirects to), applied by `Login.razor` and by `/login/start`, including on every one of that endpoint's error redirects. `/dev/login/{did}` takes the same parameter. `BoardPage` opens the named card and then strips the query by navigating to the *current* path — rebuilding it would be a route-parameter change, which the page reads as a switch of board and would close the dialog it just opened. **Link cards.** A Ponente link alone on its line in a description or a comment renders as the thing it points at — title, board › column, an excerpt, tags, due date, comment count. `RichText` splits (fences and four-space indents excluded), `RichTextView` renders, `LinkCard` resolves. `LinkPreviewService` is the only card-reading path in the app that access-checks, because it's the only one whose id comes from text a member typed; missing and denied both fall back to the plain link, on purpose. **Open Graph** tags describe the app and never the page — an unfurler is an anonymous fetch by somebody else's server and this board is private. ![The card dialog: share button in the header, and a link to another card rendered as that card inside a comment](https://git.grandiras.net/attachments/4e1125e3-ee93-4abf-b1f1-d13d22c4e63f) ## Verified Built with .NET 10 (no new warnings), **273 unit tests pass** including the new `ShareLinkTests` / `RichTextTests` / `ReturnUrlTests`, and I ran the app for real against Postgres to drive Playwright: the new `share.spec.js` (6 tests) plus `board`, `filter`, `i18n`, `prefs`, `changelog`, `calendar`, `boards`, `mobile`, entrance, icons, install, reconnect specs all pass. Four specs fail in this container — two in `access.spec.js`, `requests.spec.js`'s full-circle test and one order-dependent `calendar-all` test — and I confirmed they fail identically on a stashed, pristine checkout: they need the PDS container and the DB-cleanup helpers, neither of which exists here. One judgement call worth flagging: the issue's "embedded content / a preview" could also have meant putting card titles into the Open Graph tags so links unfurl richly in chat apps. I didn't, because that hands a private board's contents to whatever service the link passes through; the preview is rendered inside the app instead, against the reader's own access. --- ⏱ 34m 27s · 151 turns · tokens: 285.1k in / 88.1k out (+30139.7k cache-read) 📜 [Full session transcript](https://git.grandiras.net/Grandiras/claude-bot/src/branch/claude-logs/transcripts/Grandiras/Ponente/issue-93/run-259.md) → Opened PR: https://git.grandiras.net/Grandiras/Ponente/pulls/94
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Grandiras/Ponente#93
No description provided.