-
Reads and writes
+
Knows these files
{TRACKED_FILES.map((file) => (
diff --git a/website/src/components/sections/Install.tsx b/website/src/components/sections/Install.tsx
deleted file mode 100644
index f879a00..0000000
--- a/website/src/components/sections/Install.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import { links } from '../../content/links.ts';
-import { initRun, selfRun } from '../../content/runs.ts';
-import { Button } from '../ui/Button.tsx';
-import { Code } from '../ui/Code.tsx';
-import { ArrowIcon } from '../ui/icons.tsx';
-import { Reveal } from '../ui/Reveal.tsx';
-import { Terminal } from '../ui/Terminal.tsx';
-
-const SOURCE = `git clone https://github.com/okcodes/vump
-cd vump
-cargo install --path .`;
-
-export function Install() {
- return (
-
-
-
-
-
-
-
- 08
- Install
-
-
- Two commands from here.
-
-
- vump init writes a configuration
- tracking the version files it finds. After that, a bump is one word.
-
-
-
-
-
-
-
Download a binary
-
-
- A signed universal binary for macOS, static musl builds for Linux, and Windows
- including arm64. Every release publishes checksums, and both the installer and the
- CI action verify against them.
-
-
- Releases
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- vump self update keeps the
- binary current, and --channel{' '}
- names the least mature release you will accept — a floor rather than an exact match,
- so tracking release candidates never silently moves you onto the next minor’s first
- alpha.
-
-
-
-
-
- );
-}
diff --git a/website/src/components/sections/Projects.tsx b/website/src/components/sections/Projects.tsx
deleted file mode 100644
index add9cbd..0000000
--- a/website/src/components/sections/Projects.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { MULTI_PROJECT_CONFIG } from '../../content/rules.ts';
-import { statusRun } from '../../content/runs.ts';
-import { Code } from '../ui/Code.tsx';
-import { Reveal } from '../ui/Reveal.tsx';
-import { Section } from '../ui/Section.tsx';
-import { Terminal } from '../ui/Terminal.tsx';
-
-export function Projects() {
- return (
-
-
-
-
-
-
-
-
-
-
-
-
-
- Tags that name their project
-
- Projects moving independently need distinguishable tags, or they collide the moment two
- reach the same version and nothing can say which project a pushed tag describes. One{' '}
- tag_pattern covers every
- project, or each overrides its own. If two would produce the same tag, vump says so
- rather than guessing — and CI can then pass a pushed tag straight through without
- knowing which project it names.
-
-
-
-
- One vump.toml per repository
-
- A second configuration deeper in the tree is what{' '}
- [[project]] exists to prevent:
- scattered configurations cannot be addressed by name, git operations still run against
- the enclosing repository, and a pushed tag can no longer be traced back to what produced
- it. vump refuses that rather than warning about it — a warning arrives too late to help
- once a tag exists.
-
-
-
-
- );
-}
diff --git a/website/src/components/sections/Rule.tsx b/website/src/components/sections/Rule.tsx
deleted file mode 100644
index 70aff63..0000000
--- a/website/src/components/sections/Rule.tsx
+++ /dev/null
@@ -1,47 +0,0 @@
-import { guidedRun, scriptedRun } from '../../content/runs.ts';
-import { Reveal } from '../ui/Reveal.tsx';
-import { Section } from '../ui/Section.tsx';
-import { Terminal } from '../ui/Terminal.tsx';
-
-export function Rule() {
- return (
-
-
-
-
-
- vump
- asks, then confirms the whole plan
-
-
-
-
-
-
-
-
- vump patch
- never asks anything, ever
-
-
-
-
-
-
-
-
- Anything a subcommand would otherwise have to ask about is a required flag, or an error
- that names what is missing. That is what makes vump safe to put in a script, a CI job, or
- an agent’s toolbelt: a command either has everything it needs, or it fails saying what it
- does not.
-
-
-
- );
-}
diff --git a/website/src/components/sections/Safety.tsx b/website/src/components/sections/Safety.tsx
deleted file mode 100644
index 51252c3..0000000
--- a/website/src/components/sections/Safety.tsx
+++ /dev/null
@@ -1,36 +0,0 @@
-import { GUARANTEES } from '../../content/rules.ts';
-import { Reveal } from '../ui/Reveal.tsx';
-import { Section } from '../ui/Section.tsx';
-
-export function Safety() {
- return (
-
-
- {GUARANTEES.map((guarantee, index) => (
-
-
-
✓
-
{guarantee.title}
-
{guarantee.detail}
-
-
- ))}
-
-
-
-
- Releases carry keyless build provenance, signed with a short-lived certificate from the
- workflow’s own identity and recorded in a public transparency log. There is no key to
- store, rotate, or leak.
-
-
-
- );
-}
diff --git a/website/src/components/sections/Start.tsx b/website/src/components/sections/Start.tsx
new file mode 100644
index 0000000..c2e75b3
--- /dev/null
+++ b/website/src/components/sections/Start.tsx
@@ -0,0 +1,99 @@
+import { links } from '../../content/links.ts';
+import { initRun } from '../../content/runs.ts';
+import { Button } from '../ui/Button.tsx';
+import { Code } from '../ui/Code.tsx';
+import { ArrowIcon } from '../ui/icons.tsx';
+import { Reveal } from '../ui/Reveal.tsx';
+import { Terminal } from '../ui/Terminal.tsx';
+
+const SOURCE = `git clone https://github.com/okcodes/vump
+cd vump && cargo install --path .`;
+
+export function Start() {
+ return (
+
+
+
+
+
+
+ Quick start
+
+ Three steps, once.
+
+
+
+
+
+
+
+ 01 Install it
+
+
+ Grab a binary for your platform, or build it from source.
+
+
+ Download
+
+
+
+
+
+
+
+
+
+ 02 Point it at your files
+
+
+ vump init finds the files
+ that record your version and writes them into a config you can edit.
+
+
+
+
+
+
+
+
+ 03 Bump
+
+
+ Run vump and pick a bump, or
+ name one and skip the questions.
+
+
+
+ $ vump
+ — guided
+
+
+ $ vump patch
+ — no questions
+
+
+ $ vump patch --through push
+
+
+
+
+
+
+
+
+
+ Flags, configuration and everything else live in the manual.
+
+
+ Read the docs
+
+
+
+
+
+
+ );
+}
diff --git a/website/src/components/sections/Versions.tsx b/website/src/components/sections/Versions.tsx
deleted file mode 100644
index 9192637..0000000
--- a/website/src/components/sections/Versions.tsx
+++ /dev/null
@@ -1,59 +0,0 @@
-import { REFUSALS, TRANSITIONS } from '../../content/rules.ts';
-import { Reveal } from '../ui/Reveal.tsx';
-import { Section } from '../ui/Section.tsx';
-
-export function Versions() {
- return (
-
-
-
-
- Current
- Command
- Result
-
- {TRANSITIONS.map((row) => (
-
- {row.from}
-
- vump
- {row.command}
-
- {row.result}
-
- ))}
-
-
-
-
-
-
- Refused, deliberately
-
-
-
-
-
- {REFUSALS.map((refusal, index) => (
-
-
-
✗
-
{refusal.title}
-
{refusal.reason}
-
-
- ))}
-
-
-
- );
-}
diff --git a/website/src/components/ui/Code.tsx b/website/src/components/ui/Code.tsx
index a237e6e..ba11020 100644
--- a/website/src/components/ui/Code.tsx
+++ b/website/src/components/ui/Code.tsx
@@ -41,7 +41,7 @@ export function Code({ code, lang, title, emphasis, className, copyable = true }
) : null}
-
+
{lines.map((tokens, index) => {
const highlighted = marked.has(index + 1);
diff --git a/website/src/components/ui/Section.tsx b/website/src/components/ui/Section.tsx
index 87ae3d1..619a086 100644
--- a/website/src/components/ui/Section.tsx
+++ b/website/src/components/ui/Section.tsx
@@ -5,8 +5,6 @@ import { Reveal } from './Reveal.tsx';
interface Props {
id: string;
- /** The section's number, in the instrument's own voice. */
- index: string;
eyebrow: string;
title: ReactNode;
lede?: ReactNode;
@@ -14,28 +12,25 @@ interface Props {
className?: string;
}
-export function Section({ id, index, eyebrow, title, lede, children, className }: Props) {
+export function Section({ id, eyebrow, title, lede, children, className }: Props) {
return (
-
+
-
- {index}
- {eyebrow}
-
+ {eyebrow}
-
+
{title}
{lede ? (
-
+
{lede}
) : null}
-
{children}
+
{children}
);
diff --git a/website/src/components/ui/Terminal.tsx b/website/src/components/ui/Terminal.tsx
index 14570e6..8440c52 100644
--- a/website/src/components/ui/Terminal.tsx
+++ b/website/src/components/ui/Terminal.tsx
@@ -9,7 +9,6 @@ const TONES: Record = {
ink: 'text-term-ink',
muted: 'text-term-muted',
signal: 'text-term-signal',
- warn: 'text-term-warn',
danger: 'text-term-danger',
};
diff --git a/website/src/content/files.ts b/website/src/content/files.ts
index 6b6b33a..e92fe62 100644
--- a/website/src/content/files.ts
+++ b/website/src/content/files.ts
@@ -6,8 +6,6 @@ export interface TrackedFile {
ecosystem: string;
/** Where in the file the project's own version lives. */
where: string;
- /** What is deliberately never mistaken for it. */
- guard: string;
lang: Lang;
code: string;
/** 1-based lines vump writes. */
@@ -19,7 +17,6 @@ export const TRACKED_FILES: TrackedFile[] = [
name: 'package.json',
ecosystem: 'npm',
where: 'top-level version',
- guard: 'A version nested under dependencies is never mistaken for the project’s own.',
lang: 'json',
emphasis: [3],
code: `{
@@ -34,7 +31,6 @@ export const TRACKED_FILES: TrackedFile[] = [
name: 'package-lock.json',
ecosystem: 'npm',
where: 'top-level version, and the root packages entry',
- guard: 'Both move together, because npm ci rejects a tree where they disagree.',
lang: 'json',
emphasis: [3, 7],
code: `{
@@ -52,7 +48,6 @@ export const TRACKED_FILES: TrackedFile[] = [
name: 'Cargo.toml',
ecosystem: 'Cargo',
where: '[package].version',
- guard: 'Editing the document structurally cannot reach a dependency’s own pin.',
lang: 'toml',
emphasis: [3],
code: `[package]
@@ -66,7 +61,6 @@ serde = { version = "1.0", features = ["derive"] }`,
name: 'Cargo.lock',
ecosystem: 'Cargo',
where: 'the [[package]] entry for this crate',
- guard: 'Matched by the package name the manifest declares — a locked dependency is left alone.',
lang: 'toml',
emphasis: [3],
code: `[[package]]
@@ -81,7 +75,6 @@ version = "1.0.229"`,
name: 'pyproject.toml',
ecosystem: 'Python',
where: '[project].version',
- guard: 'Requirements pinned under dependencies are requirements, not this project’s version.',
lang: 'toml',
emphasis: [3],
code: `[project]
@@ -93,7 +86,6 @@ dependencies = ["httpx>=0.28"]`,
name: 'uv.lock',
ecosystem: 'Python',
where: 'the [[package]] entry for this project',
- guard: 'Written in the same run as pyproject.toml, so the pair is never half-bumped.',
lang: 'toml',
emphasis: [3],
code: `[[package]]
@@ -105,8 +97,6 @@ source = { editable = "." }`,
name: '*.csproj',
ecosystem: '.NET',
where: ' in a ',
- guard:
- 'Not , which has four numeric parts and no pre-release. Not a .',
lang: 'xml',
emphasis: [3],
code: `
@@ -123,8 +113,6 @@ source = { editable = "." }`,
name: 'Directory.Build.props',
ecosystem: '.NET',
where: ' in a ',
- guard:
- 'Where projects sharing one version declare it. MSBuild stops at the nearest one rather than merging several.',
lang: 'xml',
emphasis: [3],
code: `
@@ -137,7 +125,6 @@ source = { editable = "." }`,
name: 'VERSION',
ecosystem: 'any',
where: 'the whole file',
- guard: 'The escape hatch: any ecosystem, any build system, one line.',
lang: 'toml',
emphasis: [1],
code: `1.4.0`,
diff --git a/website/src/content/rules.ts b/website/src/content/rules.ts
index b9531c4..14cf76f 100644
--- a/website/src/content/rules.ts
+++ b/website/src/content/rules.ts
@@ -1,100 +1,3 @@
-export interface Transition {
- from: string;
- command: string;
- result: string;
-}
-
-/** The version state machine, in the order the manual states it. */
-export const TRANSITIONS: Transition[] = [
- { from: '1.2.3', command: 'patch', result: '1.2.4' },
- { from: '1.2.3', command: 'minor', result: '1.3.0' },
- { from: '1.2.3', command: 'major', result: '2.0.0' },
- { from: '1.2.3', command: 'alpha --toward minor', result: '1.3.0-alpha.0' },
- { from: '1.2.3-alpha.0', command: 'alpha', result: '1.2.3-alpha.1' },
- { from: '1.2.3-alpha.2', command: 'beta', result: '1.2.3-beta.0' },
- { from: '1.2.3-rc.1', command: 'release', result: '1.2.3' },
-];
-
-export interface Refusal {
- title: string;
- reason: string;
-}
-
-export const REFUSALS: Refusal[] = [
- {
- title: 'Moving to a less mature channel',
- reason:
- 'rc back to beta. There is no flag to force it, because there is no workflow that wants it.',
- },
- {
- title: 'patch, minor or major while on a pre-release',
- reason:
- 'It is ambiguous between finalizing and abandoning. Run release first, then bump from there.',
- },
- {
- title: 'A pre-release from a stable version',
- reason:
- 'Without --toward, nothing says which release it precedes. The flag is required, not guessed at.',
- },
-];
-
-export interface Guarantee {
- title: string;
- detail: string;
-}
-
-export const GUARANTEES: Guarantee[] = [
- {
- title: 'A dirty tree stops the run',
- detail:
- 'Checked before anything is written, not after — so a refused release leaves the working tree exactly as it found it.',
- },
- {
- title: 'Only declared files are staged',
- detail:
- 'Unrelated work in the tree cannot ride along in a version-bump commit, however it got there.',
- },
- {
- title: 'Nothing is half-applied',
- detail:
- 'Everything knowable before writing is checked before writing, so a run that cannot finish cleanly does nothing at all.',
- },
- {
- title: 'Partial success is reported exactly',
- detail:
- 'A push that fails after the commit and tag exits 8 and prints the command that finishes the job by hand.',
- },
- {
- title: 'Downloads are verified',
- detail:
- 'Every release publishes SHA256SUMS, checked before a binary is written to disk or run. A release publishing none is refused, not warned about.',
- },
- {
- title: 'No package manager is ever run',
- detail:
- 'No dependency resolution, no registry, no install scripts. An in-place edit that needs neither a network nor a dependency graph.',
- },
-];
-
-export interface ExitCode {
- code: number;
- meaning: string;
-}
-
-export const EXIT_CODES: ExitCode[] = [
- { code: 0, meaning: 'Success' },
- { code: 1, meaning: 'Unexpected failure, a declined prompt, or an update is available' },
- { code: 2, meaning: 'Usage error' },
- { code: 3, meaning: 'Configuration missing or invalid' },
- { code: 4, meaning: 'Version mismatch — check failed' },
- { code: 5, meaning: 'Tracked files disagree with each other' },
- { code: 6, meaning: 'Working tree dirty' },
- { code: 7, meaning: 'Invalid version transition' },
- { code: 8, meaning: 'Git operation failed' },
- { code: 9, meaning: 'A release artifact could not be trusted' },
- { code: 10, meaning: 'A release could not be obtained' },
-];
-
export const CI_WORKFLOW = `on:
push:
tags: ['v*']
@@ -104,18 +7,13 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
-
- # First, so a tag that lies costs nothing.
- uses: okcodes/vump/.github/actions/check@main
with:
version: \${{ github.ref_name }}
- run: cargo build --release`;
-export const MULTI_PROJECT_CONFIG = `[git]
-tag_pattern = "{project}-v{new_version}"
-
-[[project]]
+export const MULTI_PROJECT_CONFIG = `[[project]]
name = "api"
files = ["services/api/Cargo.toml", "Cargo.lock"]
@@ -126,7 +24,6 @@ files = ["apps/web/package.json"]`;
export const CONFIG = `files = ["VERSION", "ui/package.json"]
[git]
-through = "tag" # none, commit, tag, or push
+through = "tag"
commit_message = "chore: bump version to v{new_version}"
-tag_pattern = "v{new_version}"
-release_branches = ["main"]`;
+tag_pattern = "v{new_version}"`;
diff --git a/website/src/content/runs.ts b/website/src/content/runs.ts
index 4fca591..6121044 100644
--- a/website/src/content/runs.ts
+++ b/website/src/content/runs.ts
@@ -1,13 +1,12 @@
/**
- * Every pane on this page is what the binary prints.
+ * The command output shown on the page.
*
- * The marks, the column widths, the wording of the errors and the blank lines
- * between them are copied from vump's own renderer rather than invented for the
- * screenshot. A site that shows friendlier output than the tool produces is
- * advertising something that does not exist.
+ * These reproduce what the binary actually prints — the marks, the column
+ * alignment, the wording. Showing friendlier output than the tool produces
+ * would be advertising something that does not exist.
*/
-import { bad, caution, cmd, dim, gap, out, sig, t, type Line } from '../lib/terminal.ts';
+import { bad, cmd, dim, gap, out, sig, t, type Line } from '../lib/terminal.ts';
/** A patch bump carried through to a tag. */
export const bumpRun: Line[] = [
@@ -17,13 +16,10 @@ export const bumpRun: Line[] = [
out(dim(' Cargo.lock')),
out(sig('✓ '), t('committed '), dim('chore: bump version to v0.2.1')),
out(sig('✓ '), t('tagged '), sig('v0.2.1')),
- gap(),
- out(dim('To push:')),
- out(dim(' git push && git push origin v0.2.1')),
];
/** A tag that does not describe the source it claims to. */
-export const checkFailRun: Line[] = [
+export const checkRun: Line[] = [
cmd('vump check v1.4.0'),
out(bad('✗ '), t('version mismatch: expected 1.4.0')),
gap(),
@@ -31,54 +27,12 @@ export const checkFailRun: Line[] = [
out(sig(' ✓ '), t('package.json '), t('1.4.0')),
];
-/** The same check, on a tag that tells the truth. */
-export const checkPassRun: Line[] = [
- cmd('vump check v1.4.0'),
- out(sig('✓ '), t('2 files match 1.4.0')),
-];
-
-/** Omitting a subcommand: vump asks, then shows the whole plan before acting. */
-export const guidedRun: Line[] = [
- cmd('vump'),
- out(caution('? '), t('Current version 1.2.3. Bump to: '), sig('patch -> 1.2.4')),
- out(caution('? '), t('Carry the release through: '), sig('Tag — commit and tag')),
- gap(),
- out(dim(' Bumping: '), t('1.2.3 -> '), sig('1.2.4')),
- out(dim(' File: '), t('Cargo.toml')),
- out(dim(' File: '), t('Cargo.lock')),
- out(dim(' Commit: '), t('chore: bump version to v1.2.4')),
- out(dim(' Tag: '), t('v1.2.4')),
- out(dim(' Push: '), t('no')),
- gap(),
- out(caution('? '), t('Proceed? '), sig('Yes')),
- out(sig('✓ '), t('1.2.3 -> '), sig('1.2.4')),
-];
-
-/** Naming one: no questions, and a missing decision is an error instead. */
-export const scriptedRun: Line[] = [
- cmd('vump patch --through tag'),
- out(sig('✓ '), t('1.2.3 -> '), sig('1.2.4')),
+/** Adopting a repository: init finds the version files and writes them down. */
+export const initRun: Line[] = [
+ cmd('vump init'),
+ out(sig('✓ '), t('wrote vump.toml tracking:')),
out(dim(' Cargo.toml')),
- out(sig('✓ '), t('committed '), dim('chore: bump version to v1.2.4')),
- out(sig('✓ '), t('tagged '), sig('v1.2.4')),
- gap(),
- cmd('vump alpha'),
- out(bad('error: '), t('1.2.3 is stable, so `alpha` needs to know which')),
- out(t('release it leads to; pass --toward patch, --toward minor,')),
- out(t('or --toward major')),
-];
-
-/** Files that have drifted apart, and the one command that repairs them. */
-export const repairRun: Line[] = [
- cmd('vump patch'),
- out(bad('error: '), t('tracked files disagree about the current version:')),
- out(t(' VERSION '), bad('1.2.3')),
- out(t(' package.json '), bad('0.9.0')),
- gap(),
- cmd('vump set 2.0.0'),
- out(sig('✓ '), t('set to '), sig('2.0.0')),
- out(dim(' VERSION')),
- out(dim(' package.json')),
+ out(dim(' Cargo.lock')),
];
/** Every project in the repository, at a glance. */
@@ -87,28 +41,6 @@ export const statusRun: Line[] = [
out(sig('✓ '), t('api '), sig('1.4.2')),
out(sig('✓ '), t('web '), sig('0.9.0')),
gap(),
- cmd('vump minor --project api'),
- out(sig('✓ '), t('1.4.2 -> '), sig('1.5.0')),
- out(dim(' services/api/Cargo.toml')),
- out(dim(' Cargo.lock')),
-];
-
-/** Keeping the binary current. */
-export const selfRun: Line[] = [
- cmd('vump self status'),
- out(t('0.8.0 is available; running 0.7.0.')),
- out(dim('Run `vump self update` to install it.')),
- gap(),
- cmd('vump self update'),
- out(sig('✓ '), t('updated 0.7.0 -> '), sig('0.8.0')),
-];
-
-/** Adopting a repository: init finds the version files and writes them down. */
-export const initRun: Line[] = [
- cmd('vump init'),
- out(sig('✓ '), t('wrote vump.toml tracking:')),
- out(dim(' Cargo.toml')),
- out(dim(' Cargo.lock')),
- gap(),
- out(dim('Review it, then run `vump` to bump.')),
+ cmd('vump patch --project api'),
+ out(sig('✓ '), t('1.4.2 -> '), sig('1.4.3')),
];
diff --git a/website/src/index.css b/website/src/index.css
index f830e90..a9950b0 100644
--- a/website/src/index.css
+++ b/website/src/index.css
@@ -159,6 +159,20 @@
}
}
+@layer base {
+ /*
+ * Grid and flex items default to min-width:auto: they refuse to shrink below
+ * their content. One holding a horizontally scrolling pane — a terminal, a
+ * snippet, the file strip — therefore sizes its track to the pane's full
+ * width and drags the document, fixed masthead included, wider than the
+ * screen. Every layout here wants the opposite, and opting in per item means
+ * one forgotten class reintroduces the bug.
+ */
+ :where(.grid, .flex) > * {
+ min-width: 0;
+ }
+}
+
@layer components {
/* Section numbers, field labels, table headers: the instrument's own voice. */
.label {
diff --git a/website/src/lib/terminal.ts b/website/src/lib/terminal.ts
index 454ecf1..9521fa6 100644
--- a/website/src/lib/terminal.ts
+++ b/website/src/lib/terminal.ts
@@ -7,7 +7,7 @@
* that matters (the version that changed) instead of on the whole line.
*/
-export type Tone = 'ink' | 'muted' | 'signal' | 'warn' | 'danger';
+export type Tone = 'ink' | 'muted' | 'signal' | 'danger';
export interface Seg {
text: string;
@@ -30,8 +30,6 @@ export const t = seg('ink');
export const dim = seg('muted');
/** The accent. Reserved for what changed, and for a passing mark. */
export const sig = seg('signal');
-/** A waived guard or a caution. */
-export const caution = seg('warn');
/** A refusal or a failing mark. */
export const bad = seg('danger');