Links to a post or thread that open in the web and the desktop app #289

Closed
opened 2026-10-22 14:41:29 +00:00 by littlefox · 2 comments
Owner

Approved by the user for 0.7 on 2026-10-22 (D-063). They asked specifically how links can work in both the web and the desktop app.

Goal: "Kopiera länk" on a post or thread gives a link that opens that post in NeoKOM, for anyone who can read it, in the browser or in the desktop app.

LysKOM text numbers are only unique within one server, so a link always carries the server. Protocol A has a name for this: the server's canonical-name aux item [31], "the official domain name of the server, optionally followed by a colon and the port number … globally unique among all LysKOM servers". The Message-ID standard in Protocol A uses it the same way. If a server hasn't set it, we use the host (and port, if not 4894) that the desktop app connected to, or the web deployment's own server.

Core: <server>/text/<N>, e.g. kom.lysator.liu.se/text/12345. The same pattern works for /conf/N and /person/N, since markup::Ref already has all three kinds.

A link always names a post. A link to a thread is a link to its root. The post opens in its thread (the chat view from #283, or the classic view), scrolled to the post and highlighted briefly. Opening a link doesn't mark anything read; the existing read-when-seen rule applies (#180).

Three ways to write it

Form Example Used for
Web link (what "Kopiera länk" copies) https://neokom.lysator.liu.se/#kom.lysator.liu.se/text/12345 Chat, email, anywhere. Works for people without the desktop app
LysKOM reference <text 12345: Ämne> Inside posts. The Emacs client and NeoKOM already show these as links
Desktop scheme (step 4) lyskom://kom.lysator.liu.se/text/12345 Opening the desktop app from the OS: other apps, terminals, "Öppna i appen"

Why the target goes after #:

  • The web server never sees the target: no Caddy routes, nothing about the post in its logs, and no change to how page files are found.
  • It works on any static host and under a subpath.
  • Reload and offline already work, since the service worker serves the cached page.
  • The browser's back button works.

Web

  • At start and on hashchange, parse the target.
  • If the user isn't logged in, keep the target through login (sessionStorage) and open it afterwards.
  • If the link names another server, say so ("Länken gäller kom.example.org; du är inloggad på …") rather than open the post with the same number here.

Desktop

  • Recognise NeoKOM web links and lyskom:// links pasted into Ctrl+K, and clicked inside posts. Those open in the app instead of the browser.
  • The desktop app connects to the link's server name, so it can open a web link from any NeoKOM deployment.

Both

  • <text N> references and NeoKOM links pasted into the composer become <text N: subject>, so Emacs users get a working link too.

Errors

  • A post that doesn't exist and one the user can't read give the same server error. Show one message for both ("Inlägg 12345 finns inte eller går inte att läsa"); that doesn't reveal that a secret post exists.

Steps

  1. kom_client::link: parse and format all three forms; read canonical-name from server info at login. Unit tests, and fuzz the parser.

  2. Copy and open, both builds: "Kopiera länk" in a post's ··· menu and in the thread header; opening from Ctrl+K and from links in posts; the composer rewrite; the error and other-server messages.

  3. Web: read the hash, keep the target through login, handle hashchange.

  4. Desktop lyskom://:

    • MimeType=x-scheme-handler/lyskom and Exec=neokom %u in neokom.desktop, checked in all four packages;
    • neokom <url> on the command line;
    • one instance: a second launch passes the link to the running app over a socket in $XDG_RUNTIME_DIR and exits, so you don't get two logins.

    This is the largest step and can go last.

Tests

  • Unit tests: parse and format round trips, ports, ids that don't parse.
  • e2e:
    • open a link when logged out → log in → the post is shown and highlighted;
    • change the hash while logged in;
    • a link for another server;
    • a post that can't be read;
    • copying a link;
    • a pasted link becoming <text N>.
  • Desktop: neokom lyskom://… under Xvfb in desktop-smoke, with a second launch handing over to the first.

Before starting

  • A logged-out probe (allowed by D-007) to see whether kom.lysator.liu.se sets canonical-name.
  • Size: M rather than S, mostly because of step 4.
Approved by the user for 0.7 on 2026-10-22 (D-063). They asked specifically how links can work in both the web and the desktop app. Goal: "Kopiera länk" on a post or thread gives a link that opens that post in NeoKOM, for anyone who can read it, in the browser or in the desktop app. ## What a link names LysKOM text numbers are only unique within one server, so a link always carries the server. Protocol A has a name for this: the server's `canonical-name` aux item [31], "the official domain name of the server, optionally followed by a colon and the port number … globally unique among all LysKOM servers". The Message-ID standard in Protocol A uses it the same way. If a server hasn't set it, we use the host (and port, if not 4894) that the desktop app connected to, or the web deployment's own server. Core: `<server>/text/<N>`, e.g. `kom.lysator.liu.se/text/12345`. The same pattern works for `/conf/N` and `/person/N`, since `markup::Ref` already has all three kinds. A link always names a post. A link to a thread is a link to its root. The post opens in its thread (the chat view from #283, or the classic view), scrolled to the post and highlighted briefly. Opening a link doesn't mark anything read; the existing read-when-seen rule applies (#180). ## Three ways to write it | Form | Example | Used for | |---|---|---| | Web link (what "Kopiera länk" copies) | `https://neokom.lysator.liu.se/#kom.lysator.liu.se/text/12345` | Chat, email, anywhere. Works for people without the desktop app | | LysKOM reference | `<text 12345: Ämne>` | Inside posts. The Emacs client and NeoKOM already show these as links | | Desktop scheme (step 4) | `lyskom://kom.lysator.liu.se/text/12345` | Opening the desktop app from the OS: other apps, terminals, "Öppna i appen" | **Why the target goes after `#`:** - The web server never sees the target: no Caddy routes, nothing about the post in its logs, and no change to how page files are found. - It works on any static host and under a subpath. - Reload and offline already work, since the service worker serves the cached page. - The browser's back button works. ## How each client opens a link **Web** - At start and on `hashchange`, parse the target. - If the user isn't logged in, keep the target through login (sessionStorage) and open it afterwards. - If the link names another server, say so ("Länken gäller kom.example.org; du är inloggad på …") rather than open the post with the same number here. **Desktop** - Recognise NeoKOM web links and `lyskom://` links pasted into Ctrl+K, and clicked inside posts. Those open in the app instead of the browser. - The desktop app connects to the link's server name, so it can open a web link from any NeoKOM deployment. **Both** - `<text N>` references and NeoKOM links pasted into the composer become `<text N: subject>`, so Emacs users get a working link too. **Errors** - A post that doesn't exist and one the user can't read give the same server error. Show one message for both ("Inlägg 12345 finns inte eller går inte att läsa"); that doesn't reveal that a secret post exists. ## Steps 1. **`kom_client::link`:** parse and format all three forms; read `canonical-name` from server info at login. Unit tests, and fuzz the parser. 2. **Copy and open, both builds:** "Kopiera länk" in a post's ··· menu and in the thread header; opening from Ctrl+K and from links in posts; the composer rewrite; the error and other-server messages. 3. **Web:** read the hash, keep the target through login, handle hashchange. 4. **Desktop `lyskom://`:** - `MimeType=x-scheme-handler/lyskom` and `Exec=neokom %u` in `neokom.desktop`, checked in all four packages; - `neokom <url>` on the command line; - one instance: a second launch passes the link to the running app over a socket in `$XDG_RUNTIME_DIR` and exits, so you don't get two logins. This is the largest step and can go last. ## Tests - Unit tests: parse and format round trips, ports, ids that don't parse. - e2e: - open a link when logged out → log in → the post is shown and highlighted; - change the hash while logged in; - a link for another server; - a post that can't be read; - copying a link; - a pasted link becoming `<text N>`. - Desktop: `neokom lyskom://…` under Xvfb in desktop-smoke, with a second launch handing over to the first. ## Before starting - A logged-out probe (allowed by D-007) to see whether kom.lysator.liu.se sets `canonical-name`. - Size: M rather than S, mostly because of step 4.
littlefox added this to the 0.7: Threads milestone 2026-10-22 14:41:29 +00:00
Author
Owner

Probe (logged out, one get-info call, D-007): kom.lysator.liu.se sets canonical-name — aux item 31 on the server info, "kom.lysator.liu.se". So links name the server as kom.lysator.liu.se. Starting with step 1 (kom_client::link).

Probe (logged out, one get-info call, D-007): kom.lysator.liu.se sets canonical-name — aux item 31 on the server info, "kom.lysator.liu.se". So links name the server as kom.lysator.liu.se. Starting with step 1 (kom_client::link).
littlefox referenced this issue from a commit 2026-10-23 01:34:49 +00:00
Author
Owner

Verified, merged in #304; main green (run #557).

  • kom_client::link: unit tests for parsing and formatting web links, lyskom:// and <text N>, including random input (it never panics).
  • e2e/tests/share-links.spec.ts, all three profiles: "Kopiera länk" in a post's menu copies the web link; opening that link logged out keeps it through the login and then opens the post in its thread, highlighted and not marked read; a hashchange opens another; a link for another server says so rather than open the same number here; a link pasted into the composer becomes <text N: Ämne>.
  • Desktop: neokom.desktop registers x-scheme-handler/lyskom; the desktop smoke test (CI's desktop job) checks that a second launch with a link hands it to the running app, which comes to the front.
  • The server name is canonical-name (aux 31): kom.lysator.liu.se sets it (logged-out probe); the devserver seed sets dev.neokom.test.
Verified, merged in #304; main green (run #557). - `kom_client::link`: unit tests for parsing and formatting web links, `lyskom://` and `<text N>`, including random input (it never panics). - `e2e/tests/share-links.spec.ts`, all three profiles: "Kopiera länk" in a post's menu copies the web link; opening that link logged out keeps it through the login and then opens the post in its thread, highlighted and not marked read; a `hashchange` opens another; a link for another server says so rather than open the same number here; a link pasted into the composer becomes `<text N: Ämne>`. - Desktop: `neokom.desktop` registers `x-scheme-handler/lyskom`; the desktop smoke test (CI's `desktop` job) checks that a second launch with a link hands it to the running app, which comes to the front. - The server name is `canonical-name` (aux 31): `kom.lysator.liu.se` sets it (logged-out probe); the devserver seed sets `dev.neokom.test`.
littlefox referenced this issue from a commit 2026-10-24 13:23:56 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
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
littlefox/neokom#289
No description provided.