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
18 changes: 18 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
# Changelog

## 0.1.26 - 2026-08-21

- Move Settings, the release-notes dialog, project home and the project
directory onto one visual contract: shared spacing rhythm, radius scale, type
scale and button hierarchy, replacing four competing row containers, two
untokenised nav rails and two loading treatments for the same card grid.
- Show the provider beside the model on every task row, and state which task an
unassigned route inherits from instead of a bare "Auto".
- Fix the project-directory header above ~1100px, where the description
rendered beside the title instead of below it, ran under the brandmark and
squeezed the primary action between them.
- Restore the model count beside each provider. All four translations bound the
placeholder as `{0}` while the source message names it `{catalogSize}`, so the
number rendered empty. A catalog parity test now fails on that class of
mismatch, which nothing in lint, typecheck or the test suites could see.
- Stop previewing non-raster attachments through an object URL, and remove a
duplicate nested "Updates" landmark introduced with the new section headers.

## 0.1.25 - 2026-08-20

- Split provider configuration into three layers that cannot be collapsed: the
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ They consume the same `.cutout` state. They do not control each other through GU

## Install the macOS app

Download the Apple Silicon DMG from [Cutout v0.1.25](https://github.com/Nebutra/cutout/releases/tag/v0.1.25), then drag Cutout into Applications.
Download the Apple Silicon DMG from [Cutout v0.1.26](https://github.com/Nebutra/cutout/releases/tag/v0.1.26), then drag Cutout into Applications.

The public macOS build is Developer ID signed, Apple notarized, and stapled before publication.

Expand All @@ -32,15 +32,15 @@ Windows, updater-signature, reviewer, and privacy release gates.
Use Codex CLI `0.144.5` or a compatible plugin-enabled version.

```bash
codex plugin marketplace add Nebutra/cutout --ref v0.1.25
codex plugin marketplace add Nebutra/cutout --ref v0.1.26
codex plugin add cutout@cutout-local
codex plugin list
```

`codex plugin list` should show:

```text
cutout@cutout-local installed, enabled 0.1.25
cutout@cutout-local installed, enabled 0.1.26
```

Codex captures plugin Skills and MCP tools when a conversation starts. Open a new conversation after installing or updating the plugin.
Expand Down
4 changes: 2 additions & 2 deletions cutout.agent-capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"product": {
"name": "Cutout",
"positioning": "Agent-native Design OS with a visual workbench and repo-native control plane",
"packageVersion": "0.1.25"
"packageVersion": "0.1.26"
},
"protocol": {
"control": "cutout.control.v1",
Expand Down Expand Up @@ -120,7 +120,7 @@
"No cloud collaboration or remote project service.",
"No arbitrary command execution, process spawning, absolute-path scans or arbitrary export destinations.",
"No provider executor or bundled Coding workspace is available in the default CLI/MCP headless host; coding.* requires injected controlled adapters and tool.invoke returns capability-required.",
"The separate 0.1.25 Commerce production binary is restricted to evaluator-signed held-out packages, opaque private jobs and a fixed native command union. Its adjacent signed credential setup accepts only one bounded secret for the fixed Commerce Provider into an operator-only Keychain namespace; neither binary is exposed through cutout.control.v1, CLI or MCP or acts as a generic headless Provider executor.",
"The separate 0.1.26 Commerce production binary is restricted to evaluator-signed held-out packages, opaque private jobs and a fixed native command union. Its adjacent signed credential setup accepts only one bounded secret for the fixed Commerce Provider into an operator-only Keychain namespace; neither binary is exposed through cutout.control.v1, CLI or MCP or acts as a generic headless Provider executor.",
"The desktop Commerce workbench has a separate Project mode for one bounded ordinary product record, offline catalogs and one to three local product images. It executes the fixed eleven-role Commerce DAG through an enabled keyed first-party DashScope Provider with native receipts, semantic QA, retained-byte review and browser downloads. Project receipts carry no evaluator commitment and do not produce benchmark or productionReady claims; this Provider workflow is not exposed through cutout.control.v1, CLI or MCP.",
"The desktop codex-system adapter has an internal native-owned zero-tool turn path with isolated staged context, negotiated app-server schema checks and sanitized terminal evidence. Public conversation binding and turnExecution remain capability-required until a signed packaged turn succeeds against a healthy real upstream.",
"The desktop app has an internal durable local Agent Host, a native-routed Provider executor, and a bounded static-site Coding backend under an opaque host-managed workspace. Those desktop lifecycle bindings are not exposed through cutout.control.v1, CLI, or MCP, and no arbitrary-shell executor is provided.",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "cutout",
"private": true,
"license": "Apache-2.0",
"version": "0.1.25",
"version": "0.1.26",
"type": "module",
"scripts": {
"dev": "vite",
Expand Down
2 changes: 1 addition & 1 deletion plugins/cutout/.codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "cutout",
"version": "0.1.25",
"version": "0.1.26",
"description": "Repo-native Cutout design control for Codex",
"author": {
"name": "Cutout"
Expand Down
4 changes: 2 additions & 2 deletions plugins/cutout/runtime-data/cutout.agent-capabilities.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"product": {
"name": "Cutout",
"positioning": "Agent-native Design OS with a visual workbench and repo-native control plane",
"packageVersion": "0.1.25"
"packageVersion": "0.1.26"
},
"protocol": {
"control": "cutout.control.v1",
Expand Down Expand Up @@ -120,7 +120,7 @@
"No cloud collaboration or remote project service.",
"No arbitrary command execution, process spawning, absolute-path scans or arbitrary export destinations.",
"No provider executor or bundled Coding workspace is available in the default CLI/MCP headless host; coding.* requires injected controlled adapters and tool.invoke returns capability-required.",
"The separate 0.1.25 Commerce production binary is restricted to evaluator-signed held-out packages, opaque private jobs and a fixed native command union. Its adjacent signed credential setup accepts only one bounded secret for the fixed Commerce Provider into an operator-only Keychain namespace; neither binary is exposed through cutout.control.v1, CLI or MCP or acts as a generic headless Provider executor.",
"The separate 0.1.26 Commerce production binary is restricted to evaluator-signed held-out packages, opaque private jobs and a fixed native command union. Its adjacent signed credential setup accepts only one bounded secret for the fixed Commerce Provider into an operator-only Keychain namespace; neither binary is exposed through cutout.control.v1, CLI or MCP or acts as a generic headless Provider executor.",
"The desktop Commerce workbench has a separate Project mode for one bounded ordinary product record, offline catalogs and one to three local product images. It executes the fixed eleven-role Commerce DAG through an enabled keyed first-party DashScope Provider with native receipts, semantic QA, retained-byte review and browser downloads. Project receipts carry no evaluator commitment and do not produce benchmark or productionReady claims; this Provider workflow is not exposed through cutout.control.v1, CLI or MCP.",
"The desktop codex-system adapter has an internal native-owned zero-tool turn path with isolated staged context, negotiated app-server schema checks and sanitized terminal evidence. Public conversation binding and turnExecution remain capability-required until a signed packaged turn succeeds against a healthy real upstream.",
"The desktop app has an internal durable local Agent Host, a native-routed Provider executor, and a bounded static-site Coding backend under an opaque host-managed workspace. Those desktop lifecycle bindings are not exposed through cutout.control.v1, CLI, or MCP, and no arbitrary-shell executor is provided.",
Expand Down
2 changes: 1 addition & 1 deletion plugins/cutout/runtime/runtime-build.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"protocol": "cutout.codex-plugin-runtime.v1",
"packageVersion": "0.1.25",
"packageVersion": "0.1.26",
"sourceHashes": {
"node_modules/.pnpm/@tauri-apps+api@2.11.1/node_modules/@tauri-apps/api/core.js": "b2187a1c0c0a25806dc64f8823757ce553c09d3bbe3e409bd05cab32e245f1c9",
"node_modules/.pnpm/@tauri-apps+api@2.11.1/node_modules/@tauri-apps/api/external/tslib/tslib.es6.js": "76dace0a1ae47c1d3e604591b8a15db2f534b776009b9e68e786dae7fe763f70",
Expand Down
2 changes: 1 addition & 1 deletion scripts/build-commerce-operator.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const version = validateReleaseVersions({
bundledAgentCapabilities: bundledCapabilities.product?.packageVersion,
bundledRuntime: runtimeBuild.packageVersion,
},
expected: '0.1.25',
expected: '0.1.26',
})
if (JSON.stringify(capabilities) !== JSON.stringify(bundledCapabilities)) {
throw new Error('Bundled Agent capability manifest drifted from the release source.')
Expand Down
24 changes: 12 additions & 12 deletions scripts/commerce-held-out-evaluator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,14 @@ function pending() {
}))
return {
commitment: {
challengeSelection: { payload: { expiresAt: 2_000, hostBuildVersion: '0.1.25' } },
challengeSelection: { payload: { expiresAt: 2_000, hostBuildVersion: '0.1.26' } },
},
completionRequest: {
protocol: 'cutout.commerce-held-out-evaluator-completion.v2',
challengeId: 'challenge:test',
challengeHash: HASH,
evaluatorKeyId: 'evaluator:minisign:test',
hostBuildVersion: '0.1.25',
hostBuildVersion: '0.1.26',
commitmentHash: HASH,
inputManifestHash: HASH,
runId: 'run:test',
Expand Down Expand Up @@ -79,7 +79,7 @@ async function inspectablePending() {
protocol: 'cutout.commerce-held-out-challenge-selection.v2',
benchmark: { id: 'benchmark:commerce-profile:p1-p7', version: 2 },
profile: { id: 'profile:commerce-materials', version: '1.1.0' },
hostBuildVersion: '0.1.25',
hostBuildVersion: '0.1.26',
challengeId,
challengeNonce: 'n'.repeat(32),
inputManifestHash,
Expand All @@ -92,7 +92,7 @@ async function inspectablePending() {
},
challengeHash,
evaluatorKeyId,
hostBuildVersion: '0.1.25',
hostBuildVersion: '0.1.26',
inputManifest: {
schema: 'commerce.held-out-input-manifest.v1',
rehearsalIdentity: bundle.identity,
Expand All @@ -118,7 +118,7 @@ async function inspectablePending() {
challengeId,
challengeHash,
evaluatorKeyId,
hostBuildVersion: '0.1.25',
hostBuildVersion: '0.1.26',
commitmentHash,
inputManifestHash,
runId: bundle.runId,
Expand Down Expand Up @@ -333,15 +333,15 @@ describe('independent Commerce evaluator handoff (not benchmark evidence)', () =
const payload = createChallengePayload({
inputManifestHash: HASH,
evaluatorKeyId: 'evaluator:minisign:test',
hostBuildVersion: '0.1.25',
hostBuildVersion: '0.1.26',
ttlMinutes: 30,
now: 1_000,
})
expect(payload).toMatchObject({
protocol: 'cutout.commerce-held-out-challenge-selection.v2',
inputManifestHash: HASH,
evaluatorKeyId: 'evaluator:minisign:test',
hostBuildVersion: '0.1.25',
hostBuildVersion: '0.1.26',
issuedAt: 1_000,
expiresAt: 1_801_000,
})
Expand All @@ -351,11 +351,11 @@ describe('independent Commerce evaluator handoff (not benchmark evidence)', () =

it('derives one authoritative evaluator build version and rejects package/Cargo drift', () => {
expect(resolveAuthoritativeHostBuildVersion({
packageVersion: '0.1.25',
cargoVersion: '0.1.25',
})).toBe('0.1.25')
packageVersion: '0.1.26',
cargoVersion: '0.1.26',
})).toBe('0.1.26')
expect(() => resolveAuthoritativeHostBuildVersion({
packageVersion: '0.1.25',
packageVersion: '0.1.26',
cargoVersion: '0.1.19',
})).toThrow(/drifted/)
})
Expand All @@ -373,7 +373,7 @@ describe('independent Commerce evaluator handoff (not benchmark evidence)', () =
expect(validateAcceptedReview(accepted, value)).toEqual(accepted)
expect(createCompletionPayload({ pending: value, review: accepted, completedAt: 1_600 })).toMatchObject({
challengeId: 'challenge:test',
hostBuildVersion: '0.1.25',
hostBuildVersion: '0.1.26',
bundleHash: HASH,
decision: 'accepted',
deliverableCount: 11,
Expand Down
4 changes: 2 additions & 2 deletions scripts/commerce-operator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ describe('Commerce operator release identity', () => {
expect(runner).toContain('`-R=${NATIVE_HOST_MACOS_REQUIREMENT}`')
})

it('keeps product, Cargo, Tauri, capability, and plugin versions at 0.1.25', async () => {
it('keeps product, Cargo, Tauri, capability, and plugin versions at 0.1.26', async () => {
const [pkg, tauri, capabilities, bundledCapabilities, plugin, runtimeBuild, cargo] = await Promise.all([
readFile('package.json', 'utf8').then(JSON.parse),
readFile('src-tauri/tauri.conf.json', 'utf8').then(JSON.parse),
Expand All @@ -153,7 +153,7 @@ describe('Commerce operator release identity', () => {
readFile('plugins/cutout/runtime/runtime-build.json', 'utf8').then(JSON.parse),
readFile('src-tauri/Cargo.toml', 'utf8'),
])
expect(pkg.version).toBe('0.1.25')
expect(pkg.version).toBe('0.1.26')
expect(tauri.version).toBe(pkg.version)
expect(capabilities.product.packageVersion).toBe(pkg.version)
expect(bundledCapabilities).toEqual(capabilities)
Expand Down
32 changes: 24 additions & 8 deletions scripts/release-notes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,16 @@ function catalog(entries: unknown[] = [entry()]) {
}

describe('release-note catalog', () => {
it('loads the reviewed v0.1.25 entry with all five shipped locales and no historical backfill', async () => {
it('loads the reviewed v0.1.26 entry with all five shipped locales and no historical backfill', async () => {
const loaded = await loadReleaseNotesCatalog(undefined, { requireAllLocales: true })
expect(loaded.entries.map((value) => value.version)).toEqual(['0.1.25', '0.1.24', '0.1.20', '0.1.19'])
expect(Object.keys(requireReleaseNotesEntry(loaded, '0.1.25').locales)).toEqual(RELEASE_NOTES_LOCALES)
// Derived, not pinned: the head is whatever package.json ships and the tail is
// the retained history. A literal list means every release edits this test.
const shipping = JSON.parse(await readFile('package.json', 'utf8')).version as string
expect(loaded.entries[0]?.version).toBe(shipping)
expect(loaded.entries.map((value) => value.version)).toEqual(
[...new Set([shipping, ...loaded.entries.map((value) => value.version)])],
)
expect(Object.keys(requireReleaseNotesEntry(loaded, '0.1.26').locales)).toEqual(RELEASE_NOTES_LOCALES)
expect(findReleaseNotesEntry(loaded, '0.1.16')).toBeUndefined()
})

Expand Down Expand Up @@ -106,8 +112,10 @@ describe('release-note catalog', () => {
})

it('renders all release inputs through the CLI from one exact entry', async () => {
const version = JSON.parse(await readFile('package.json', 'utf8')).version as string
const catalog = await loadReleaseNotesCatalog(undefined, { requireAllLocales: true })
const output = await mkdtemp(join(tmpdir(), 'cutout-release-notes-'))
const result = spawnSync(process.execPath, ['scripts/release-notes.mjs', 'render', '--version', '0.1.25', '--output', output, '--require-all-locales'], { cwd: process.cwd(), encoding: 'utf8' })
const result = spawnSync(process.execPath, ['scripts/release-notes.mjs', 'render', '--version', version, '--output', output, '--require-all-locales'], { cwd: process.cwd(), encoding: 'utf8' })
expect(result.status, result.stderr).toBe(0)
const [plainText, updater, bundled, github] = await Promise.all([
readFile(join(output, 'updater-notes.txt'), 'utf8'),
Expand All @@ -116,9 +124,17 @@ describe('release-note catalog', () => {
readFile(join(output, 'github-release.md'), 'utf8'),
])
expect(JSON.parse(updater)).toEqual(JSON.parse(bundled))
expect(plainText).toContain('A provider no longer carries a model')
expect(plainText).toContain('Every task slot now takes effect')
expect(plainText).toContain('No call reaches a model you never configured')
expect(github).toContain('Cutout v0\\.1\\.25')
// Assert the render carries the catalog's own copy rather than a snapshot of
// it, so new release notes do not require editing this test.
const shipped = requireReleaseNotesEntry(catalog, version, { requireAllLocales: true })
for (const highlight of shipped.locales.en.highlights) {
expect(plainText).toContain(highlight.title)
}
expect(plainText).toContain(shipped.locales.en.headline)
// The renderer markdown-escapes dots, so the heading reads `Cutout v0\.1\.26`.
// Built with split/join rather than a `.replace` regex: escaping only `.` and
// not `\` is incomplete sanitization, and this assertion never needed a
// sanitizer — it needs the literal shape the renderer emits.
expect(github).toContain(`Cutout v${version.split('.').join('\\.')}`)
})
})
2 changes: 1 addition & 1 deletion src-tauri/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "app"
version = "0.1.25"
version = "0.1.26"
default-run = "app"
description = "Local-first AI design production workspace"
authors = ["Nebutra Team"]
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/src/commands/ai/commerce_held_out.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1700,7 +1700,7 @@ mod tests {
#[test]
fn challenge_window_and_exact_identity_fail_closed() {
let mut value = challenge();
assert_eq!(HOST_BUILD_VERSION, "0.1.25");
assert_eq!(HOST_BUILD_VERSION, "0.1.26");
assert!(validate_challenge_binding(&value, &"a".repeat(64), "evaluator:test", 20).is_ok());
assert!(
validate_challenge_binding(&value, &"a".repeat(64), "evaluator:test", 101).is_err()
Expand Down Expand Up @@ -1812,7 +1812,7 @@ mod tests {
fn challenge_payload_uses_the_cross_runtime_canonical_json_contract() {
let encoded = String::from_utf8(canonical_json_bytes(&challenge()).unwrap()).unwrap();
assert_eq!(encoded, format!(
"{{\"allowedRunId\":\"run:test\",\"benchmark\":{{\"id\":\"benchmark:commerce-profile:p1-p7\",\"version\":2}},\"challengeId\":\"challenge:test\",\"challengeNonce\":\"{}\",\"evaluatorKeyId\":\"evaluator:test\",\"expiresAt\":100,\"hostBuildVersion\":\"0.1.25\",\"inputManifestHash\":\"{}\",\"issuedAt\":10,\"profile\":{{\"id\":\"profile:commerce-materials\",\"version\":\"1.1.0\"}},\"protocol\":\"cutout.commerce-held-out-challenge-selection.v2\"}}",
"{{\"allowedRunId\":\"run:test\",\"benchmark\":{{\"id\":\"benchmark:commerce-profile:p1-p7\",\"version\":2}},\"challengeId\":\"challenge:test\",\"challengeNonce\":\"{}\",\"evaluatorKeyId\":\"evaluator:test\",\"expiresAt\":100,\"hostBuildVersion\":\"0.1.26\",\"inputManifestHash\":\"{}\",\"issuedAt\":10,\"profile\":{{\"id\":\"profile:commerce-materials\",\"version\":\"1.1.0\"}},\"protocol\":\"cutout.commerce-held-out-challenge-selection.v2\"}}",
"n".repeat(32), "a".repeat(64)
));
}
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/tauri.conf.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "../node_modules/@tauri-apps/cli/config.schema.json",
"productName": "Cutout",
"version": "0.1.25",
"version": "0.1.26",
"identifier": "com.nebutra.cutout",
"build": {
"frontendDist": "../dist",
Expand Down
Loading