// Readable application URLs. // // Records are addressed in visible URLs by a short code instead of their // UUID: the first eight hexadecimal characters of the id. The code is derived, // never stored, so it needs no column, no backfill and survives every rename. // Projects add their key in front (`/projects/min-3f2a9c81`) and issues use // their identifier (`?issue=MIN-42`). // // Titles never enter a URL: project, objective, page and issue names are // encrypted at rest, and paths reach request logs, analytics and referrers. // // Every resolver also accepts a full UUID, so bookmarks, pushed notifications // and persisted tabs written before this format keep opening their target. // PURE module: shared by client components, the proxy and server code. const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i; const CODE_RE = /^[0-9a-f]{8}$/i; const ISSUE_IDENTIFIER_RE = /^([A-Za-z0-9]+)-(\d+)$/; export const SHORT_CODE_LENGTH = 8; export function isUuid(value: string): boolean { return UUID_RE.test(value); } export function isShortCode(value: string): boolean { return CODE_RE.test(value); } /** The short code of a record id: its first eight hexadecimal characters. */ export function shortCode(id: string): string { return id.slice(0, SHORT_CODE_LENGTH).toLowerCase(); } /** * The reference a URL carries for `id`: its short code. Anything that is not * a UUID (a test fixture, an optimistic temporary id) is kept as is, so it * still resolves by exact equality. */ export function recordRef(id: string): string { return isUuid(id) ? shortCode(id) : id; } /** Lowercase, URL-safe form of a project key (`MIN` → `min`). */ function keySlug(key: string | null | undefined): string { return (key ?? "") .toLowerCase() .replace(/[^a-z0-9]+/g, "-") .replace(/^-+|-+$/g, ""); } export interface ProjectRefSource { id: string; key?: string | null; } // Project keys this client has loaded (fed by lib/ref-registry.ts), so a link // built from a bare `project_id` still reads `min-3f2a9c81`. Empty on the // server, where such a link carries the code alone and stays valid. const projectKeys = new Map(); export function rememberProjectKey(id: string, key: string): void { projectKeys.set(id.toLowerCase(), key); } export function knownProjectKey(id: string): string | null { return projectKeys.get(id.toLowerCase()) ?? null; } /** `min-3f2a9c81`: the project key for the eye, the code for resolution. */ export function projectRef(project: ProjectRefSource | string): string { const { id, key } = typeof project === "string" ? { id: project, key: null } : project; const ref = recordRef(id); if (!isUuid(id)) return ref; const slug = keySlug(key ?? knownProjectKey(id)); return slug ? `${slug}-${ref}` : ref; } /** * The code a project segment carries: the part after its last hyphen when * that part is a short code, the whole segment otherwise (a bare code, a full * UUID or a legacy test id). The key in front is decorative, so a renamed key * never breaks a link. */ export function projectSegmentCode(segment: string): string { const decoded = safeDecode(segment); if (isUuid(decoded)) return decoded.toLowerCase(); const tail = decoded.slice(decoded.lastIndexOf("-") + 1); return CODE_RE.test(tail) ? tail.toLowerCase() : decoded; } /** * Does the URL reference `ref` designate the record `id`? A full UUID must * match exactly, a short code matches the id it prefixes, and any other value * (non-UUID ids) matches by equality. */ export function refMatches(id: string, ref: string): boolean { if (id === ref) return true; if (isUuid(ref)) return id.toLowerCase() === ref.toLowerCase(); return CODE_RE.test(ref) && isUuid(id) && shortCode(id) === ref.toLowerCase(); } /** * The record a URL reference designates among those the viewer can see. * Within one user's scope two ids sharing 32 bits are vanishingly rare; if it * ever happens the reference is ambiguous and resolves to nothing rather than * to the wrong record. */ export function resolveRef( ref: string | null | undefined, items: readonly T[] | null | undefined, ): T | undefined { if (!ref || !items) return undefined; const exact = items.find((item) => item.id === ref); if (exact) return exact; const matches = items.filter((item) => refMatches(item.id, ref)); return matches.length === 1 ? matches[0] : undefined; } /** The project a `/projects/` route designates, if visible. */ export function resolveProjectSegment( segment: string | null | undefined, projects: readonly T[] | null | undefined, ): T | undefined { return segment ? resolveRef(projectSegmentCode(segment), projects) : undefined; } export type ProjectTab = "objectives" | "pages" | "triage" | "feedback" | "settings"; /** `/projects/min-3f2a9c81[/tab]`. */ export function projectPath(project: ProjectRefSource | string, tab?: ProjectTab): string { const base = `/projects/${encodeURIComponent(projectRef(project))}`; return tab ? `${base}/${tab}` : base; } type QueryValue = string | number | null | undefined; /** A path with its query; record values become short codes by the caller. */ export function withQuery(path: string, query?: Record): string { if (!query) return path; const params = new URLSearchParams(); for (const [name, value] of Object.entries(query)) { if (value !== null && value !== undefined && value !== "") params.set(name, String(value)); } const search = params.toString(); return search ? `${path}?${search}` : path; } /** A project route with selection parameters: `/projects/min-3f2a9c81?objective=8b1e04aa`. */ export function projectHref( project: ProjectRefSource | string, tab?: ProjectTab, query?: Record, ): string { return withQuery(projectPath(project, tab), query); } /** `/projects/min-3f2a9c81/pages/8b1e04aa`. */ export function pagePath(project: ProjectRefSource | string, pageId: string): string { return `${projectPath(project, "pages")}/${encodeURIComponent(recordRef(pageId))}`; } export interface IssueRefSource { id: string; number?: number | null; project_id?: string | null; /** The project key, when the caller has it at hand. */ key?: string | null; } /** * The value of `?issue=`: the readable identifier when the project key and * number are known (`MIN-42`), the short code otherwise. */ export function issueRef(issue: IssueRefSource): string { const key = issue.key ?? (issue.project_id ? knownProjectKey(issue.project_id) : null); return key && issue.number != null ? `${key}-${issue.number}` : recordRef(issue.id); } /** The board with an issue open: `/projects/min-3f2a9c81?issue=MIN-42`. */ export function issueHref( project: ProjectRefSource | string, issue: IssueRefSource, tab?: ProjectTab, ): string { const key = typeof project === "string" ? null : project.key; return projectHref(project, tab, { issue: issueRef({ key, ...issue }) }); } /** An objective's details: `/projects/min-3f2a9c81/objectives?open=8b1e04aa`. */ export function objectiveHref(project: ProjectRefSource | string, objectiveId: string): string { return projectHref(project, "objectives", { open: recordRef(objectiveId) }); } /** An objective's tickets on the board: `/projects/min-3f2a9c81?objective=8b1e04aa`. */ export function objectiveBoardHref(project: ProjectRefSource | string, objectiveId: string): string { return projectHref(project, undefined, { objective: recordRef(objectiveId) }); } /** A feedback post: `/projects/min-3f2a9c81/feedback?post=8b1e04aa`. */ export function feedbackPostHref(project: ProjectRefSource | string, postId: string): string { return projectHref(project, "feedback", { post: recordRef(postId) }); } /** `/routines?routine=8b1e04aa`. */ export function routineHref(routineId: string): string { return withQuery("/routines", { routine: recordRef(routineId) }); } /** `/pull-requests?pr=8b1e04aa`. */ export function pullRequestHref(pullRequestId: string): string { return withQuery("/pull-requests", { pr: recordRef(pullRequestId) }); } /** * The issue a `?issue=` value designates: an identifier such as `MIN-42` * (matched on the number, the key being the project's), a short code or a * full UUID. */ export function resolveIssueRef( ref: string | null | undefined, issues: readonly T[] | null | undefined, ): T | undefined { if (!ref || !issues) return undefined; const exact = issues.find((issue) => issue.id === ref); if (exact) return exact; const identifier = ISSUE_IDENTIFIER_RE.exec(ref); if (identifier && !CODE_RE.test(ref)) { const number = Number(identifier[2]); const key = identifier[1].toLowerCase(); // On a board spanning projects the key tells same-numbered issues apart. return issues.find((issue) => { if (issue.number !== number) return false; const issueKey = issue.project_id ? knownProjectKey(issue.project_id) : null; return !issueKey || issueKey.toLowerCase() === key; }); } return resolveRef(ref, issues); } /** Inclusive UUID bounds of a short code, for an indexed range lookup. */ export function shortCodeRange(code: string): { from: string; to: string } | null { if (!CODE_RE.test(code)) return null; const c = code.toLowerCase(); return { from: `${c}-0000-0000-0000-000000000000`, to: `${c}-ffff-ffff-ffff-ffffffffffff`, }; } function safeDecode(value: string): string { try { return decodeURIComponent(value); } catch { return value; } } const LEGACY_PARAMS = ["issue", "objective", "open", "post", "routine", "pr", "family", "view"] as const; /** * The readable form of a legacy app URL, or null when it already is one. * Replaces every full UUID the URL carries (project and page segments, record * parameters) by its short code. The project key cannot be known without a * read; the client adds it once the project list is loaded. */ export function shortenLegacyAppUrl(pathname: string, search: string): string | null { const segments = pathname.split("/"); let changed = false; const shortened = segments.map((segment) => { if (!isUuid(segment)) return segment; changed = true; return shortCode(segment); }); const params = new URLSearchParams(search); for (const name of LEGACY_PARAMS) { const value = params.get(name); if (value && isUuid(value)) { params.set(name, shortCode(value)); changed = true; } } if (!changed) return null; const query = params.toString(); return `${shortened.join("/")}${query ? `?${query}` : ""}`; }