Merge public-gh/master into paperclip-issue-documents

Resolve conflicts by keeping the issue-documents work alongside upstream heartbeat-context, worktree branding, and adapter runtime updates.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Dotta
2026-03-13 21:47:06 -05:00
64 changed files with 4620 additions and 292 deletions

View File

@@ -0,0 +1,65 @@
import {
extractCompanyPrefixFromPath,
normalizeCompanyPrefix,
toCompanyRelativePath,
} from "./company-routes";
const GLOBAL_SEGMENTS = new Set(["auth", "invite", "board-claim", "docs"]);
export function isRememberableCompanyPath(path: string): boolean {
const pathname = path.split("?")[0] ?? "";
const segments = pathname.split("/").filter(Boolean);
if (segments.length === 0) return true;
const [root] = segments;
if (GLOBAL_SEGMENTS.has(root!)) return false;
return true;
}
function findCompanyByPrefix<T extends { id: string; issuePrefix: string }>(params: {
companies: T[];
companyPrefix: string;
}): T | null {
const normalizedPrefix = normalizeCompanyPrefix(params.companyPrefix);
return params.companies.find((company) => normalizeCompanyPrefix(company.issuePrefix) === normalizedPrefix) ?? null;
}
export function getRememberedPathOwnerCompanyId<T extends { id: string; issuePrefix: string }>(params: {
companies: T[];
pathname: string;
fallbackCompanyId: string | null;
}): string | null {
const routeCompanyPrefix = extractCompanyPrefixFromPath(params.pathname);
if (!routeCompanyPrefix) {
return params.fallbackCompanyId;
}
return findCompanyByPrefix({
companies: params.companies,
companyPrefix: routeCompanyPrefix,
})?.id ?? null;
}
export function sanitizeRememberedPathForCompany(params: {
path: string | null | undefined;
companyPrefix: string;
}): string {
const relativePath = params.path ? toCompanyRelativePath(params.path) : "/dashboard";
if (!isRememberableCompanyPath(relativePath)) {
return "/dashboard";
}
const pathname = relativePath.split("?")[0] ?? "";
const segments = pathname.split("/").filter(Boolean);
const [root, entityId] = segments;
if (root === "issues" && entityId) {
const identifierMatch = /^([A-Za-z]+)-\d+$/.exec(entityId);
if (
identifierMatch &&
normalizeCompanyPrefix(identifierMatch[1] ?? "") !== normalizeCompanyPrefix(params.companyPrefix)
) {
return "/dashboard";
}
}
return relativePath;
}

View File

@@ -0,0 +1,65 @@
export type WorktreeUiBranding = {
enabled: true;
name: string;
color: string;
textColor: string;
};
function readMetaContent(name: string): string | null {
if (typeof document === "undefined") return null;
const element = document.querySelector(`meta[name="${name}"]`);
const content = element?.getAttribute("content")?.trim();
return content ? content : null;
}
function normalizeHexColor(value: string | null): string | null {
if (!value) return null;
const hex = value.startsWith("#") ? value.slice(1) : value;
if (/^[0-9a-fA-F]{3}$/.test(hex)) {
return `#${hex.split("").map((char) => `${char}${char}`).join("").toLowerCase()}`;
}
if (/^[0-9a-fA-F]{6}$/.test(hex)) {
return `#${hex.toLowerCase()}`;
}
return null;
}
function hexToRgb(color: string): { r: number; g: number; b: number } {
const normalized = normalizeHexColor(color) ?? "#000000";
return {
r: Number.parseInt(normalized.slice(1, 3), 16),
g: Number.parseInt(normalized.slice(3, 5), 16),
b: Number.parseInt(normalized.slice(5, 7), 16),
};
}
function relativeLuminanceChannel(value: number): number {
const normalized = value / 255;
return normalized <= 0.03928 ? normalized / 12.92 : ((normalized + 0.055) / 1.055) ** 2.4;
}
function pickReadableTextColor(background: string): string {
const { r, g, b } = hexToRgb(background);
const luminance =
(0.2126 * relativeLuminanceChannel(r)) +
(0.7152 * relativeLuminanceChannel(g)) +
(0.0722 * relativeLuminanceChannel(b));
const whiteContrast = 1.05 / (luminance + 0.05);
const blackContrast = (luminance + 0.05) / 0.05;
return whiteContrast >= blackContrast ? "#f8fafc" : "#111827";
}
export function getWorktreeUiBranding(): WorktreeUiBranding | null {
if (readMetaContent("paperclip-worktree-enabled") !== "true") return null;
const name = readMetaContent("paperclip-worktree-name");
const color = normalizeHexColor(readMetaContent("paperclip-worktree-color"));
if (!name || !color) return null;
return {
enabled: true,
name,
color,
textColor: normalizeHexColor(readMetaContent("paperclip-worktree-text-color")) ?? pickReadableTextColor(color),
};
}