11// Copyright 2026 Parity Technologies (UK) Ltd.
22// SPDX-License-Identifier: AGPL-3.0-only
33
4- // Which page the shell is: a product by its `.dot` label, a local or preview target in debug builds, or no product at
5- // all. Read from the URL alone.
4+ // Which page the shell is: a product by its `.dot` label in the hostname or the path , a local or preview target in debug
5+ // builds, or no product at all. Read from the URL alone.
66
77import { BASE_DOMAIN } from '@dotli/config' ;
8- import { dotNsUrl } from '@dotli/resolver' ;
8+ import { dotNsUrl , isValidDotLabel } from '@dotli/resolver' ;
99import { log } from '@dotli/shared' ;
1010import { parseDotLabel } from './dot-label.js' ;
1111import { parsePreviewTargetUrl } from './preview-route.js' ;
@@ -43,7 +43,12 @@ export interface LocalTarget {
4343}
4444
4545export type ShellRoute =
46- | { kind : 'product' ; label : string }
46+ | {
47+ kind : 'product' ;
48+ label : string ;
49+ /** The page path ahead of the product's own: `/<label>` where the path names the product, else empty. */
50+ base : string ;
51+ }
4752 | LocalTarget
4853 /** A nested dot.li, which would draw a second topbar. */
4954 | { kind : 'nested' }
@@ -56,7 +61,7 @@ export function readShellRoute(location: ShellLocation, options: RouteOptions):
5661 }
5762 const label = parseDotLabel ( location . hostname ) ;
5863 if ( label !== null ) {
59- return { kind : 'product' , label } ;
64+ return { kind : 'product' , label, base : '' } ;
6065 }
6166 const productId = options . localhost ? readProductIdOverride ( location . search ) : undefined ;
6267 if ( previewUrl !== null ) {
@@ -66,7 +71,18 @@ export function readShellRoute(location: ShellLocation, options: RouteOptions):
6671 if ( localhostUrl !== null ) {
6772 return { kind : 'localhost' , url : localhostUrl , host : new URL ( localhostUrl ) . host , productId } ;
6873 }
69- return { kind : 'landing' } ;
74+ return readPathProduct ( location . pathname ) ?? { kind : 'landing' } ;
75+ }
76+
77+ /** A link to a product on the bare host: `/<label>` or `/<label>.dot`, then the product's own path. */
78+ function readPathProduct ( pathname : string ) : ShellRoute | null {
79+ const segment = / ^ \/ ( [ ^ / ] + ) / . exec ( pathname ) ?. [ 1 ] ;
80+ if ( segment === undefined ) {
81+ return null ;
82+ }
83+ // Lowercased as the browser lowercases a hostname, so both spellings of a link reach the same name.
84+ const label = segment . replace ( / \. d o t $ / i, '' ) . toLowerCase ( ) ;
85+ return isValidDotLabel ( label ) ? { kind : 'product' , label, base : `/${ segment } ` } : null ;
7086}
7187
7288function readProductIdOverride ( search : string ) : string | undefined {
0 commit comments