Pick any line to see why it is the way it is: the commit, the pull request and issue it came from, and what the agent was thinking.
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 1 | /** |
| 2 | * Links into the app. A `g1t://` link names a site path, as on the desktop | |
| 3 | * (apps/desktop/src-tauri/src/links.rs): `g1t://acme/-/chat/dm/chn_1` is | |
| 4 | * the page `/acme/-/chat/dm/chn_1`. The app has screens for some of the | |
| 5 | * site's pages; `routeFor` says which screen shows a site path, and the | |
| 6 | * rest open on the site. | |
| 7 | * | |
| 8 | * Kept free of React Native imports so `npm test` runs it under Node. | |
| 9 | */ | |
| 10 | ||
| 11 | /** Where the app's OAuth sign-in comes back to. */ | |
| 12 | export const OAUTH_CALLBACK_PATH = "/oauth/callback"; | |
| 13 | ||
| Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell. | 14 | /** Customize's tabs, each a screen of the app's Customize. */ |
| 15 | const CUSTOMIZE_TABS = new Set(["skills", "connectors", "plugins"]); | |
| 16 | ||
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 17 | /** Path segments a workspace or account can never be named (the site's own pages). */ |
| 18 | const NOT_OWNERS = new Set(["settings", "notifications", "oauth", "device", "login", "register", "-"]); | |
| 19 | ||
| 20 | /** | |
| 21 | * The app's screen for a site path, or null when the app has none and the | |
| 22 | * page should open on the site. | |
| 23 | */ | |
| 24 | export function routeFor(sitePath: string): string | null { | |
| 25 | const [pathname, query = ""] = sitePath.split("?", 2); | |
| 26 | const parts = pathname.split("/").filter(Boolean).map(decodeURIComponentSafe); | |
| 27 | const keep = query ? `?${query}` : ""; | |
| 28 | if (parts.length === 0) return "/"; | |
| 29 | if (`/${parts.join("/")}` === OAUTH_CALLBACK_PATH) return OAUTH_CALLBACK_PATH + keep; | |
| 30 | const [owner, second, third, fourth, fifth] = parts; | |
| The phone app with your own g1t, on g1t.sh/download until Google Play, and Node 24 (#5) | 31 | // `g1t://connect?server=<site>`: point the app at a g1t, as a self-hosted one's download page offers. |
| 32 | if (owner === "connect" && !second && /(^|&)server=/.test(query)) return `/connect?${query}`; | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 33 | if (NOT_OWNERS.has(owner.toLowerCase())) return null; |
| 34 | const workspace = `workspace=${encodeURIComponent(owner)}`; | |
| 35 | ||
| 36 | // A workspace's own pages: /<workspace>/-/<page>. | |
| 37 | if (second === "-") { | |
| 38 | if (third === "chat") { | |
| 39 | if (!fourth) return `/chat?${workspace}`; | |
| 40 | if (fourth === "dm" && fifth) return `/chat/${encodeURIComponent(fifth)}?${workspace}`; | |
| 41 | if (fourth !== "browse" && fourth !== "dm" && !fifth) return `/chat/${encodeURIComponent(fourth)}?${workspace}`; | |
| 42 | return null; | |
| 43 | } | |
| 44 | if (third === "overview" && !fourth) return `/code?${workspace}`; | |
| Merge branch 'customize' into main: Customize replaces the Marketplace, the Artifacts trash and spaces changes, the Security-free cleanup, with the desktop app's own title bar kept in the shell. | 45 | // Customize's tabs open the app's Customize, on the same tab; its other |
| 46 | // pages (a plugin, a connector, requests) open on the site. The | |
| 47 | // Marketplace, the integrations settings and the old skill library were | |
| 48 | // Customize before it had the name. | |
| 49 | if (third === "customize") { | |
| 50 | if (!fourth) return `/customize?${workspace}`; | |
| 51 | if (CUSTOMIZE_TABS.has(fourth) && !fifth) return `/customize/${fourth}?${workspace}`; | |
| 52 | return null; | |
| 53 | } | |
| 54 | if (third === "marketplace" || third === "integrations" || (third === "agents" && fourth === "skills" && !fifth)) { | |
| The phone app: Chat in full with phone notifications, plus Code and Customize (#4) | 55 | return `/customize?${workspace}`; |
| 56 | } | |
| 57 | return null; | |
| 58 | } | |
| 59 | ||
| 60 | // A repository's pages: /<owner>/<repo>[/pull/<n> | /issues/<n>]. | |
| 61 | if (!second) return null; | |
| 62 | const repo = `/code/${encodeURIComponent(owner)}/${encodeURIComponent(second)}`; | |
| 63 | if (!third) return repo; | |
| 64 | if (third === "pull" && fourth && /^\d+$/.test(fourth) && !fifth) return `${repo}/pull/${fourth}`; | |
| 65 | if (third === "issues" && fourth && /^\d+$/.test(fourth) && !fifth) return `${repo}/issues/${fourth}`; | |
| 66 | return null; | |
| 67 | } | |
| 68 | ||
| 69 | /** | |
| 70 | * The site path a `g1t://` link names, or null for any other link. | |
| 71 | * `g1t://acme/-/chat?x` is `/acme/-/chat?x`. | |
| 72 | */ | |
| 73 | export function sitePathOf(link: string): string | null { | |
| 74 | const match = /^g1t:\/\/([^?#]*)(\?[^#]*)?/i.exec(link); | |
| 75 | if (!match) return null; | |
| 76 | const path = `/${match[1].replace(/^\/+/, "")}`; | |
| 77 | return path + (match[2] ?? ""); | |
| 78 | } | |
| 79 | ||
| 80 | function decodeURIComponentSafe(part: string): string { | |
| 81 | try { | |
| 82 | return decodeURIComponent(part); | |
| 83 | } catch { | |
| 84 | return part; | |
| 85 | } | |
| 86 | } |
This file's history is long; its oldest lines are credited to the oldest commit read.