Skip to content

feat(sheets): add sheet-to-sheet hyperlinks#498

Open
the-narwhal wants to merge 4638 commits into
ProtonMail:mainfrom
the-narwhal:sheet-to-sheet-hyperlinks
Open

feat(sheets): add sheet-to-sheet hyperlinks#498
the-narwhal wants to merge 4638 commits into
ProtonMail:mainfrom
the-narwhal:sheet-to-sheet-hyperlinks

Conversation

@the-narwhal
Copy link
Copy Markdown

Sheet-to-sheet hyperlinks

Implements the ability to create a clickable link in one sheet that navigates to another sheet within the same file — a frequently requested feature from the Proton community.


⚠️ Testing note

I was unable to run or build the application locally (node_modules not installed in this environment), so this PR has not been manually tested end-to-end. It has been statically reviewed and audited for correctness, but it should go through your normal QA pass before merging.

This is intended as a reference implementation, feel free to merge it as-is, iterate on top of it, or simply use it as a starting point. All design decisions are documented below and open to feedback.


What changed

sheetLink.ts (new)
A small utility module for encoding and decoding internal sheet links. Links are stored as the string #sheet:<sheetId>, using the numeric ID rather than the sheet name so that links survive renames. The parser uses a strict round-trip check (String(parseInt(raw)) === raw) to reject malformed values like #sheet:1abc or #sheet:1.5.

InsertLink.tsx
A segmented "Web link / Sheet in this file" toggle is added above the existing URL input. Selecting the sheet tab shows an Ariakit Select dropdown populated from sheets.list (visible sheets in tab order, matching the bottom bar). Opening the dialog on a cell that already carries a sheet link pre-selects the correct tab and sheet.

One correctness fix was required here: the original two-effect initialisation pattern (one to read the existing hyperlink, one to default to the first sheet) caused a React flush-time race where the defaulting branch overwrote the parsed sheet ID. These are now merged into a single effect.

CellTooltip.tsx
LinkInfo detects #sheet: links and handles them differently from external URLs:

External URL Sheet link
Icon globe grid-2
Display text raw URL sheet name (live, reflects renames)
Click opens via event bus showSheet() if hidden, then setActiveId()
Copy copies URL copies #sheet:<id> string
Copy disabled in read-only yes (pre-existing) no — copying is non-mutating

Sheet name resolution uses sheets.listIncludingHidden so that links pointing to a hidden sheet still display the correct name and remain clickable (clicking auto-unhides the sheet, matching the tab switcher behaviour in BottomBar). If the target sheet has been deleted the tooltip falls back to "Unknown sheet" and the click is a no-op.


Design decisions open to feedback

  • Link format #sheet:<id> is stored as a plain string through the existing onInsertLink API. This avoids any changes to the @rowsncolumns data layer but means the format lives entirely in our layer. An alternative would be to use the library's native { kind, location } object format if onInsertLink ever starts accepting it.

  • No cell reference the current implementation links to a sheet only, not to a specific cell or range. Extending the format to #sheet:<id>!A1 and switching the click handler to goToCell() would be a natural follow-up.

  • Hidden sheets in the picker the dropdown intentionally shows only visible sheets (sheets.list). If linking to a hidden sheet is a desired workflow, the picker could switch to sheets.listIncludingHidden with a visual indicator for hidden entries.

Raphael Rychetsky and others added 30 commits April 21, 2026 09:15
Prevent concurrent referral spotlight feature PUTs

See merge request web/clients!24282
VPNPLG-58: Add payload and telemetry when forking the session

See merge request web/clients!24135
fix: fix icon alignment in cancellation flow

See merge request web/clients!24315
Add offer card UI components

See merge request web/clients!24302
added temporary need content and config

See merge request web/clients!24307
feat: workspace premium cancelation flow

See merge request web/clients!24319
Add news subscription for Meet

See merge request web/clients!24209
use the same setting menu for extension as for web

See merge request web/clients!24241
INWEB-867: Add common setup for Slack API in the CI

See merge request web/clients!24271
Add new permissions modals and stop requesting permissions automatically

See merge request web/clients!24248
DRVWEB: Search blobs encryption/decryption

See merge request web/clients!23888
[IDTEAM-5613] Add telemetry for sharing

See merge request web/clients!24221
Limit sharing settings visibility only to admins on parent share

See merge request web/clients!24326
[DRVWEB-4974] Album listing sdk

See merge request web/clients!23946
Enable telemetry for Meet

See merge request web/clients!24331
MargeBot and others added 24 commits April 24, 2026 09:01
Idteam 5873 fix safari too small popup

See merge request web/clients!24390
Add `justify-start-when-stacked` table helper

See merge request web/clients!24428
Prevent missing navigator.mediaDevices to throw

See merge request web/clients!24429
Update category counts when labelling a message from a conversation

See merge request web/clients!24325
Update openSubscriptionModal usage

See merge request web/clients!24107
Fix a bug storing feature flags in cookie

See merge request web/clients!24454
[DRVWEB-4967] delete legacy drive view

See merge request web/clients!24263
NOISSUE: Adapt telemetry for tv flow

See merge request web/clients!24405
Defer update prompt if user is in meeting

See merge request web/clients!24450
Mail BYOE: hide lock icons in sent messages when `X-Pm-Byoe` header is present

See merge request web/clients!24000
PostQuantumOptInModal: prompt for auth before committing the opt-in

See merge request web/clients!24457
Add LoginLink member creation mode

See merge request web/clients!24448
Allows users to create a hyperlink in one sheet that navigates to another
sheet within the same file. Selecting "Sheet in this file" in the Insert
Link dialog presents a dropdown of all sheets; clicking the resulting
link in the cell tooltip switches the active tab.

Internal links are stored as the string "#sheet:<sheetId>" using the
numeric ID rather than the sheet name, so links survive renames.

Changes
-------
- Add sheetLink.ts: encode/decode "#sheet:<id>" strings with strict
  round-trip validation (rejects "#sheet:1abc", "#sheet:1.5", etc.)

- InsertLink.tsx: add a "Web link / Sheet in this file" segmented toggle
  above the existing URL input. The sheet tab shows an Ariakit Select
  dropdown populated from sheets.list (visible sheets in tab order).
  Opening the dialog on a cell that already has a sheet link pre-selects
  the correct tab and sheet. Both branches share the existing Apply /
  Cancel flow; Cancel now reuses the already-captured close ref instead
  of re-invoking useUI.$ at render time.

  The two initialisation effects are merged into one to prevent a React
  flush-time race where the defaulting branch (set first sheet) fired
  after the parse branch in the same render, overwriting the linked
  sheet ID with sheets[0].

- CellTooltip.tsx: LinkInfo detects internal links and handles them
  differently from external URLs:
  - Icon: grid-2 instead of globe
  - Display text: sheet name (resolved live from sheets.listIncludingHidden
    so renames are reflected immediately)
  - Click: calls sheets.show() if the target is hidden, then setActiveId()
    — matching the behaviour of the sheet tab switcher in BottomBar
  - Copy link: copies the raw "#sheet:<id>" string, not the display name,
    so the value can be round-tripped back into the dialog
  - Copy link is no longer disabled in read-only mode (non-mutating action)
  - useSheetLinkInfo looks up sheets from listIncludingHidden so links to
    hidden sheets display the correct name and remain clickable
@the-narwhal the-narwhal marked this pull request as ready for review April 26, 2026 19:38
@mmso mmso force-pushed the main branch 5 times, most recently from 115579e to b6fd253 Compare June 3, 2026 16:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.