Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 17 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
name: Release

# Every push to main (i.e. every merged PR) must produce a new release.
# Every push to main that changes PACKAGE content must produce a new release.
# The release version is whatever sits in npm/package.json — the PR is
# responsible for bumping it. If that version was already released (its
# vX.Y.Z tag already exists), the workflow FAILS, prompting a version bump,
# because a merge to main without a version bump is a mistake under this
# contract.
# because a merge to main with package changes but no version bump is a mistake
# under this contract.
#
# The `paths-ignore` below exempts non-package changes (the landing site, docs,
# repo tooling, markdown) — those ship to main without an npm release, so a
# docs/site iteration doesn't force a version bump. If a push touches ONLY
# ignored paths, this workflow doesn't run; touch any non-ignored file and it
# does (and then demands a bump).

on:
push:
branches: [main]
paths-ignore:
- 'apps/landing/**'
- 'docs/**'
- 'assets/**'
- '.npmrc'
- '**/*.md'
- '.github/workflows/deploy-landing.yml'
- '.github/workflows/release.yml'

# Never run two releases at once; let an in-flight one finish.
concurrency:
Expand Down
7 changes: 7 additions & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Pin this repo to the public npm registry, regardless of any global ~/.npmrc
# (e.g. a corporate Artifactory mirror). This keeps `pnpm install` — and the
# resolution URLs it writes into pnpm-lock.yaml — pointed at registry.npmjs.org,
# so the lockfile stays installable on CI and never gets a private-registry host
# baked in. (In an .npmrc the key is `registry=`; the equivalent env var is
# npm_config_registry.)
registry=https://registry.npmjs.org/
19 changes: 17 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,19 @@
# mx
<p align="center">
<img src="assets/logo.svg" alt="mx logo" width="88" />
</p>

<h1 align="center">mx</h1>

<p align="center"><strong>Work on parallel features using coding agents.</strong></p>

<p align="center">
<a href="https://www.npmjs.com/package/@rousan/mx"><img src="https://img.shields.io/npm/v/@rousan/mx?color=f59e0b&label=npm" alt="npm version" /></a>
<a href="LICENSE"><img src="https://img.shields.io/npm/l/@rousan/mx?color=f59e0b&label=license" alt="license" /></a>
<img src="https://img.shields.io/node/v/@rousan/mx?color=f59e0b&label=node" alt="node version" />
<a href="https://mx.rousanali.com"><img src="https://img.shields.io/badge/docs-mx.rousanali.com-f59e0b" alt="docs" /></a>
</p>

---

Source and tooling for **mx** ("multiplexer") — a system for working on several features in parallel across multiple shared repos, using git worktrees. Each feature gets an isolated environment (its own worktrees, branches, ports, editor workspace), so you can switch between features instantly without stashing or branch-juggling.

Expand Down Expand Up @@ -114,7 +129,7 @@ Read commands take `--porcelain` for stable JSON; mutations echo the resulting o

## Release

Releases are **CI-driven**: bump `npm/package.json`'s `"version"` in a PR and merge to `main` — `.github/workflows/release.yml` runs the check pipeline, `npm publish`es from `npm/` (auth via the `NPM_TOKEN` secret), tags `vX.Y.Z`, and creates a GitHub Release. **Every merge to `main` must bump the version** or the run fails on the existing-tag guard. `pnpm release` (`scripts/release.sh`) remains a local fallback for emergencies. CI (`.github/workflows/ci.yml`) runs typecheck/lint/test/build on every PR.
Releases are **CI-driven**: bump `npm/package.json`'s `"version"` in a PR and merge to `main` — `.github/workflows/release.yml` runs the check pipeline, `npm publish`es from `npm/` (auth via the `NPM_TOKEN` secret), tags `vX.Y.Z`, and creates a GitHub Release. **Every merge to `main` that changes package content must bump the version** or the run fails on the existing-tag guard. Merges touching only non-package paths — the landing site (`apps/landing/**`), `docs/**`, markdown, or repo tooling — are exempt via the workflow's `paths-ignore`, so a docs/site iteration ships to `main` (and redeploys the site) without an npm release. `pnpm release` (`scripts/release.sh`) remains a local fallback for emergencies. CI (`.github/workflows/ci.yml`) runs typecheck/lint/test/build on every PR.

See **[docs/release.md](docs/release.md)** for the full runbook, the `NPM_TOKEN` setup, and every gotcha caught the hard way (npm name similarity, fresh-scope propagation lag, etc.).

Expand Down
18 changes: 8 additions & 10 deletions apps/landing/public/favicon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 20 additions & 18 deletions apps/landing/src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,16 @@
@custom-variant dark (&:where([data-theme='dark'], [data-theme='dark'] *));

/*
* Semantic design tokens — a calm, mostly-monochrome grayscale system with a
* single warm terra-cotta accent, in the spirit of a documentation site (and
* the sibling `ccal` project). Emphasis comes from ink weight and gray steps;
* the accent is used sparingly for the logo, one hero phrase, prompts, links,
* icon chips, and the primary button. Light values are the base; the dark
* override block below swaps them when <html data-theme="dark">. Exposing them
* through `@theme inline` lets us use them as Tailwind color utilities
* (e.g. `bg-surface`, `text-muted`, `border-line`, `text-accent`).
* Semantic design tokens — a calm, mostly-monochrome grayscale base with a warm
* amber/yellow accent drawn from the Tailwind palette (amber for the UI accent;
* the logo itself stays multi-pastel — teal/amber/rose with a violet source
* node). Emphasis comes from ink
* weight and gray steps; the accent is used sparingly for the logo, one hero
* phrase, prompts, links, icon chips, and the primary button. Light values are
* the base; the dark override block below swaps them when
* <html data-theme="dark">. Exposing them through `@theme inline` lets us use
* them as Tailwind color utilities (e.g. `bg-surface`, `text-muted`,
* `border-line`, `text-accent`).
*
* Token roles: `--accent` the terra-cotta accent (prompts, icons, dots);
* `--accent-ink` a readable accent for links; `--accent-soft` a faint accent
Expand All @@ -35,13 +37,13 @@
--text-soft: #3f3f46;
--muted: #71717a;
--faint: #a1a1aa;
--accent: #c0562f;
--accent-ink: #b04a26;
--accent-2: #c0562f;
--accent-soft: rgba(192, 86, 47, 0.1);
--accent: #d97706; /* amber-600 (readable amber on white) */
--accent-ink: #b45309; /* amber-700 (readable link on white) */
--accent-2: #d97706;
--accent-soft: rgba(245, 158, 11, 0.14); /* amber-500 @ 14% */
--accent-strong: #18181b;
--on-accent: #ffffff;
--cta: #c0562f;
--cta: #f59e0b; /* amber-500 button, white text */
--on-cta: #ffffff;
--amber: #71717a;
--code-bg: #f6f6f7;
Expand All @@ -58,13 +60,13 @@
--text-soft: #c8c8ce;
--muted: #9a9aa4;
--faint: #63636d;
--accent: #e0793f;
--accent-ink: #ea8a5a;
--accent-2: #e0793f;
--accent-soft: rgba(224, 121, 63, 0.15);
--accent: #fbbf24; /* amber-400 (bright gold on the dark canvas) */
--accent-ink: #fcd34d; /* amber-300 */
--accent-2: #fbbf24;
--accent-soft: rgba(251, 191, 36, 0.16); /* amber-400 @ 16% */
--accent-strong: #fafafa;
--on-accent: #18181b;
--cta: #c0562f;
--cta: #f59e0b; /* amber-500 button, white text */
--on-cta: #ffffff;
--amber: #a1a1aa;
--code-bg: #0d0d11;
Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/sections/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export function Footer() {
<div className="mx-auto flex w-full max-w-6xl flex-col gap-6 px-5 py-10 sm:flex-row sm:items-center sm:justify-between">
<div>
<div className="flex items-center gap-2 font-mono text-lg font-bold">
<Logo className="h-5 w-auto text-accent" />
<Logo className="h-5 w-auto" />
<span className="mx-grad-text">mx</span>
</div>
<p className="mt-1 text-sm text-muted">
Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/sections/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export function Hero() {
<div aria-hidden className="mx-lanes pointer-events-none absolute inset-0" />

<div className="relative mx-auto w-full max-w-5xl px-5 pt-16 pb-24 text-center sm:pt-20 sm:pb-32">
<Logo className="mx-auto mb-8 h-10 w-auto text-accent sm:h-12" />
<Logo className="mx-auto mb-8 h-10 w-auto sm:h-12" />

<a
href={REPO_URL}
Expand Down
2 changes: 1 addition & 1 deletion apps/landing/src/sections/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export function Nav({ theme, onToggleTheme }: { theme: Theme; onToggleTheme: ()
>
<nav className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between px-5">
<a href="#top" className="flex items-center gap-2 font-mono text-xl font-bold">
<Logo className="h-5 w-auto text-accent" />
<Logo className="h-5 w-auto" />
<span className="mx-grad-text">mx</span>
</a>

Expand Down
27 changes: 14 additions & 13 deletions apps/landing/src/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,25 +118,26 @@ const ICON_PATHS: Record<IconName, ReactNode> = {
/**
* The mx logo mark: a "multiplex" fan-out — one node branching into three
* parallel nodes — echoing mx running several features (worktrees) in parallel.
* Drawn with `currentColor` so callers set the color (the accent) via className.
* Each of the three lanes is a different pastel (Tailwind teal/amber/rose -400)
* to underline the "many parallel" idea; the source node is violet (the UI
* accent). Colors are fixed so the mark reads the same in light and dark; the
* caller controls only size via className.
*
* @param props - Standard SVG props (notably `className` for size/color).
* @returns The logo SVG, sized and colored from its className.
* @param props - Standard SVG props (notably `className` for size).
* @returns The multi-pastel logo SVG.
*/
export function Logo(props: SVGProps<SVGSVGElement>) {
return (
<svg viewBox="0 0 33 28" fill="none" aria-hidden="true" {...props}>
<g stroke="currentColor" strokeWidth={2.6} strokeLinecap="round">
<path d="M9 14 L26 6" />
<path d="M9 14 H26" />
<path d="M9 14 L26 22" />
</g>
<g fill="currentColor">
<circle cx="7" cy="14" r="3.6" />
<circle cx="26" cy="6" r="3.2" />
<circle cx="26" cy="14" r="3.2" />
<circle cx="26" cy="22" r="3.2" />
<g strokeWidth={2.6} strokeLinecap="round">
<path d="M9 14 L26 6" stroke="#2dd4bf" />
<path d="M9 14 H26" stroke="#fbbf24" />
<path d="M9 14 L26 22" stroke="#fb7185" />
</g>
<circle cx="7" cy="14" r="3.6" fill="#a78bfa" />
<circle cx="26" cy="6" r="3.2" fill="#2dd4bf" />
<circle cx="26" cy="14" r="3.2" fill="#fbbf24" />
<circle cx="26" cy="22" r="3.2" fill="#fb7185" />
</svg>
);
}
Expand Down
11 changes: 11 additions & 0 deletions assets/logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading