# Technology Stack: Fork Maintenance Approach **Project:** Nexus (fork of Paperclip) **Researched:** 2026-03-30 **Scope:** Safely maintaining a display-layer fork of a TypeScript monorepo while staying rebassable on upstream --- ## Summary Recommendation Use **git rebase with a [nexus] commit prefix convention** for fork maintenance. Extract all display strings into **a single `packages/branding/` package** that acts as the exclusive mutation surface. Keep every code identifier, route, schema, and package name unchanged. This combination minimises conflict surface to two file types: branding constants and onboarding assets. --- ## 1. Fork Maintenance Strategy ### Recommended: Rebase-Over-Upstream with Prefix Convention **Confidence: HIGH** — Used by git-for-windows, microsoft/git, and VSCodium. Standard practice for long-lived forks. **How it works:** Every Nexus-specific commit carries a `[nexus]` prefix in the commit message. On each upstream release: ```bash git fetch upstream git rebase upstream/master ``` During rebase, conflicts only appear on commits that touch the same lines as upstream changes. With display-only mutations (string constants, Markdown prose, one config file), the conflict surface is tiny. Non-conflicting commits replay cleanly. **Commit message convention:** ``` [nexus] Rename CEO→Project Manager in OnboardingWizard [nexus] Replace AGENT_ROLE_LABELS display value for ceo role [nexus] Rewrite onboarding-assets/ceo/ SOUL.md and AGENTS.md ``` The prefix does two things: it makes `[nexus]` commits immediately identifiable in `git log`, and it allows `git range-diff` to verify that a rebase correctly replayed all downstream patches. **Verification after every upstream sync:** ```bash # Compare the old and new version of the downstream patch series git range-diff upstream/master ORIG_HEAD HEAD ``` `git range-diff` shows which `[nexus]` commits changed during rebase (conflict resolutions), which replayed identically, and which were dropped. This is the standard tool used by the Git project itself for patch-series validation. **Confidence: HIGH** (official Git tooling, not a third-party tool). **Enable rerere to auto-replay recurring resolutions:** ```bash git config rerere.enabled true ``` `git rerere` records how each conflict was resolved. On the next upstream sync, if the same conflict hunk appears again (common when upstream frequently touches the same area), Git auto-resolves it identically. This eliminates repetitive manual conflict resolution. **Confidence: HIGH** (official Git feature, described in Pro Git book). **Atomic commits — most important discipline:** Each `[nexus]` commit must touch exactly one logical unit. Never mix a display-string change with a behaviour change in the same commit. Rationale: if upstream changes the same file for a different reason, a mixed commit creates conflicts in code paths you didn't mean to touch. Atomic commits mean a conflict only appears on the exact line you changed. **Confidence: HIGH** (documented in git-for-windows strategy and GitHub's friendly fork guide). --- ### Alternative Considered: git-format-patch / Quilt-style Patch Queue **What it is:** Maintain Nexus changes as a series of `.patch` files outside the tree, applied on top of a clean upstream checkout. Used by VSCodium for build-time patch application with placeholder substitution. **Why not for Nexus:** VSCodium's patch approach works because they rebuild from source on every release. Nexus is a live development fork where engineers commit code daily. Applying patches at build time would break the normal `git commit` / `git push` workflow. Rebase-over-upstream is the right model when the fork is being actively developed, not just rebranded at release time. **Confidence: MEDIUM** — VSCodium's approach is well-documented but architecturally different from a dev fork. --- ### Alternative Considered: Merge (not rebase) Merge upstream with `git merge upstream/master` produces a merge commit that interleaves upstream and Nexus history. GitHub's friendly fork guide recommends merge for multi-contributor forks. For a solo-developer fork with a small, clearly bounded patch set, rebase produces a cleaner history and makes it obvious exactly which commits are Nexus-specific. Use merge only if the team grows beyond one or two contributors. --- ## 2. String Extraction Pattern ### Recommended: Centralised Branding Package with Typed Constants **Confidence: HIGH** — Standard TypeScript monorepo pattern, no third-party risk. #### Why NOT i18n (react-i18next, LinguiJS, etc.) i18n libraries are designed for multi-locale text management. They add runtime overhead, require JSON translation files, and introduce a dependency that Paperclip upstream does not have. Importing one into a display-layer fork creates a new package.json entry that will conflict if upstream ever adds i18n itself. The simpler approach is a plain TypeScript constants module. #### The Pattern: `packages/branding/` Create a dedicated workspace package at `packages/branding/` that is the single place all display-layer strings live. Nothing else in the monorepo hardcodes Nexus-facing strings. **Package structure:** ``` packages/branding/ src/ index.ts -- re-exports everything vocabulary.ts -- entity names (Workspace, Project Manager, Owner) ui-labels.ts -- button text, page titles, sidebar labels cli-strings.ts -- CLI output messages, prompts, banner agent-roles.ts -- display labels for role constants package.json -- name: "@paperclipai/branding" (keeps @paperclipai namespace) tsconfig.json ``` **`vocabulary.ts` example:** ```typescript export const VOCAB = { // The Company entity displayed as: company: { singular: "Workspace", plural: "Workspaces", possessive: "Workspace's", }, // The CEO role displayed as: ceo: { singular: "Project Manager", short: "PM", }, // The Board role displayed as: board: { singular: "Owner", }, // Product name product: { name: "Nexus", cli: "nexus", tagline: "Your agent workspace", }, } as const; ``` **`agent-roles.ts` example — overrides `AGENT_ROLE_LABELS` from shared:** ```typescript import { AGENT_ROLE_LABELS } from "@paperclipai/shared"; // Override display labels only. Underlying keys (ceo, engineer, etc.) are unchanged. export const DISPLAY_ROLE_LABELS: typeof AGENT_ROLE_LABELS = { ...AGENT_ROLE_LABELS, ceo: "Project Manager", }; ``` **Why keep the package name `@paperclipai/branding`:** The `@paperclipai/*` namespace is used by thousands of import statements. Adding a new package under the same namespace costs nothing and avoids the namespace change that would ripple through every file. The branding package is net-new; it does not rename any existing package. **Usage in UI:** Components import from `@paperclipai/branding` instead of hardcoding strings. The existing `AGENT_ROLE_LABELS` from `@paperclipai/shared` stays unchanged; components use `DISPLAY_ROLE_LABELS` from branding instead. ```tsx // Before (upstream hardcoded): Company {AGENT_ROLE_LABELS[agent.role]} // After (Nexus): import { VOCAB, DISPLAY_ROLE_LABELS } from "@paperclipai/branding"; {VOCAB.company.singular} {DISPLAY_ROLE_LABELS[agent.role]} ``` **Usage in CLI (`cli/src/commands/onboard.ts`):** ```typescript import { VOCAB } from "@paperclipai/branding"; p.intro(`${VOCAB.product.name} setup`); // Replaces: p.intro("Paperclip setup"); ``` **Usage in server banner (`server/src/startup-banner.ts`):** ```typescript import { VOCAB } from "@paperclipai/branding"; // Replace ASCII art "PAPERCLIP" with "NEXUS" // Replace embedded CLI command text with VOCAB.product.cli references ``` #### What Stays in `@paperclipai/shared` — Unchanged The following stay exactly as upstream to preserve upstream rebasability: - `AGENT_ROLE_LABELS` (with `ceo: "CEO"`) — the authoritative map, untouched - `AGENT_ROLES` array containing `"ceo"` — these are stored values, not display strings - `APPROVAL_TYPES`, `INVITE_TYPES` — stored DB enum values, untouched - `API.companies = "/api/companies"` — route constants, untouched The branding package only **overrides at the callsite**, never modifying shared constants. --- ## 3. UI Branding / Theming Layer ### Recommended: CSS Custom Properties in Tailwind v4 + a Single `branding.css` File **Confidence: HIGH** — Tailwind v4's CSS-first config model is designed for this. Official Vite + Tailwind v4 docs confirm CSS custom properties as the standard. Paperclip already uses Tailwind CSS 4.0.7. In Tailwind v4, theme tokens are defined as CSS custom properties in the CSS file, not in a JavaScript config. This makes branding overrides a single CSS file change. **`ui/src/branding.css` (new [nexus] file):** ```css /* Nexus brand overrides — Tailwind v4 custom properties */ :root { --color-brand-primary: oklch(65% 0.2 270); /* Nexus blue-purple */ --color-brand-secondary: oklch(75% 0.15 200); } ``` Import this file once in `ui/src/main.tsx` after the main Tailwind CSS import. Zero upstream conflict risk: it is a net-new file. **Vite `define` for build-time constants:** For values injected at build time (version strings, product name in `