Skip to content
Agentmart
Submit a tool

CLAUDE.md · cartesiancs/cartcut

CLAUDE.md from cartesiancs/cartcut: 894 lines covering Writing rules, Build layout: read this first, Commands, How editing works and more.

Template by cartesiancs

IndexedMIT★ 818
Write a review

Install

Works with Claude Code. Read the repository before you give any tool access to your data.

curl -fsSL https://raw.githubusercontent.com/cartesiancs/cartcut/main/CLAUDE.md -o CLAUDE.md

Read it and adapt it to your project before use.

Get the weekly diff

The weekly diff: one email a week with new agent tools, notable updates and grade changes, plus security alerts for tools you follow. Double opt-in, one-click unsubscribe.

We use your email only for this newsletter. Privacy

About

The CLAUDE.md file that cartesiancs/cartcut (818 stars) keeps for coding agents. It has 894 lines in 13 sections: Writing rules; Build layout: read this first; Commands; How editing works; Conventions that repeat across every feature; Rendering and export; The project file, and Auto Save; Where the features live; Extensions; The Claude Code bridge; Cloud content; Testing; Known rough edges. Use it as a model for your own project's instructions.

Use cases: documentation

No reviews yet. Installed it? Reviews come from verified installers only. How reviews work

What's in it

A CLAUDE.md file: project memory Claude Code loads at the start of every session.

For
Claude Code
Length
894 lines
File
CLAUDE.md

Sections

  1. Writing rules
  2. Build layout: read this first
  3. Commands
  4. How editing works
  5. Conventions that repeat across every feature
  6. Rendering and export
  7. The project file, and Auto Save
  8. Where the features live
  9. Extensions
  10. The Claude Code bridge
  11. Cloud content
  12. Testing
  13. Known rough edges

First lines (shown under the repo's MIT license)

# Cartcut

An Electron video editor (`cartcut-app`, formerly "nugget"). Lit web components
and vanilla zustand in the renderer, plain TypeScript in the main process,
FFmpeg for export.

This file is a map and a list of invariants. It is loaded into every session, so
keep it short: record what a reader cannot get from the code in a minute, and
nothing else. Design history, changelogs and "how we know it works" belong in
commit messages and in the tests.

## Writing rules

**Never use an em-dash (`—`) or a middle dot (`·`).** Not in code comments, not
in documentation, not in commit messages, not in UI strings, not here. Write
what the dash was standing in for: a comma or brackets for an aside, a colon for
a consequence, a semicolon or two sentences for two joined thoughts, `1 to 10`
for a range, `a * b` for multiplication, a real list for a list. An en-dash
(`–`) is fine in a numeric range and nowhere else; a hyphen is always fine.

Two habits for the same reason: no "It's not X, it's Y" constructions, and no
rhetorical flourish where a fact belongs. A comment earns its length by naming
the specific failure the line below it prevents.

## Build layout: read this first

`electron/` is **source**; `main/` is its **compiled output**, and `package.json`
points `"main"` at `main/main.js`. Edit `electron/`, never `main/`.

**`electron/` may not import from `apps/app/src`.** `.tsconfig/tsconfig.json`
pins `rootDir: ../electron`; one such import widens it, the build relocates from
`main/` to `main/electron/`, and the app stops finding its entry point. This is
why the MCP tools talk to the renderer over IPC instead of calling the editing
functions directly, and why the recorder tray crosses the boundary as data.

`apps/app` has no `package.json`; the root webpack config builds it. The folders
under `apps/` and `packages/` that *do* have one are standalone Vite apps with
their own lockfiles. This is not an npm workspace.

`server/` is the **Rust cloud content server** (`cargo test` there), not
`electron/server/`, the Express server the app runs for the self-hosted web
build. It has its own `Cargo.lock`, and `package.json` excludes it from the asar.

`plugins/cartcut-editing/` is **published to users**, through the marketplace
declared at `.claude-plugin/marketplace.json`. Its skill is the one the app's
users get; it is no longer under `.claude/`, so editing it changes what ships.
The plugin has to stay a subdirectory: a plugin's root `package.json` is
installed by Claude Code, and pointing a plugin at the repository root would
make installing it build the whole Electron app. The two versions, in
`plugin.json` and in the marketplace entry, have to agree; `claude plugin tag`
checks that, `claude plugin validate <path> --strict` checks the rest.

The same skill ships a **second way**, through `npx skills add
cartesiancs/cartcut`, which finds it by parsing `marketplace.json` and following
the plugin's `./`-prefixed `source`. That path and that prefix are load-bearing
for a route no `claude plugin` command exercises, and that route installs the
skill alone: it does not read `.mcp.json`, so its users connect the bridge with
the ⚡ panel's `claude mcp add` line. `npx skills add cartesiancs/cartcut --list`
is the check, and must report one skill.

A third route needs no install: the bridge serves the same file as the MCP
resource `skill://cartcut-editing/SKILL.md`, read from `app.asar` on every
request, and its instructions tell the model to read it when the skill is not
in its own list (`electron/mcp/skillResource.ts`). The skill's path is
load-bearing here too, and `skillResource.test.ts` pins it.

FFmpeg and ffprobe live in `./bin/<platform>-<arch>/` (`darwin-arm64`,
`darwin-x64`, `win32-x64`); `electron/lib/ffmpeg.ts` picks by `process.arch`.
The macOS binaries must be **native**: an x86_64 ffmpeg runs under Rosetta at
roughly half speed and says nothing. `lipo -archs bin/darwin-arm64/ffmpeg`.

## Commands

```
npm run dev      # tsc --watch (main) + webpack --watch (renderer)
npm run start    # electron .   (run in a second terminal)
npm run dev:hot  # dev + start:hot in one terminal: reload on every build
npm test         # vitest run
npx tsc --noEmit -p ./.tsconfig    # typecheck the main process
npx webpack --mode=development     # build the renderer once
npm run build:overlay              # the screen recorder's own Vite app
npm run build:speech               # the native STT sidecar (Swift, macOS only)
npm run build:input                # the native mouse monitor (Swift, macOS only)
```

**Hot reload is development-only and opt-in.** `start:hot`
(`scripts/devElectron.mjs`) restarts Electron when `main/` changes and sets
`CARTCUT_HOT_RELOAD=1`; `electron/lib/devReload.ts` then reloads the editor
window on a new renderer bundle (or swaps `style.css` alone in place), but only
when `isDev` is also true. A reload drops the document and the undo history, so
`npm run start` and the e2e harness never get it.

`npm run dev` does **not** build `apps/overlay-record`; build it yourself after
touching it or the recorder windows load a stale bundle. It does build
`native/cartcut-stt` and `native/cartcut-input` (a staleness check plus two
`swiftc` calls each) and skips them loudly on Windows, or for the speech one
without the macOS 26 SDK.

## How editing works

The most important convention in the codebase. Every edit is a **pure function
`(TimelineDocument) => TimelineDocument`**, applied through
`useTimelineStore.withCheckpoint(fn)`, which records one undo step.

**A pure op that declines returns its input, by identity.** `withCheckpoint`
reads that as "nothing happened" and records no step, which is what makes a
split off the end of a clip or a drag into an occupied slot cost the user
nothing. Preserve it in any new op, and give every new op a co-located suite
that covers the decline path as well as the happy one.

```
apps/app/src/@types/timeline.ts          element shapes, FILETYPES, animatable properties
apps/app/src/features/timeline/tracks.ts     TimelineDocument, tracks, z-order
apps/app/src/features/timeline/geometry.ts   trim/duration/speed invariants
apps/app/src/features/timeline/clipOps.ts    split, trim, move, delete, removeRanges
apps/app/src/features/timeline/placement.ts  where a new element lands
apps/app/src/states/timelineStore.ts         the store, undo history
```

Two things about time that are easy to get wrong:

- `trim` is a window into the **source file**, in source ms.
  `duration === trim.endTime - trim.startTime`.
- The clip occupies `[startTime, startTime + duration/speed)` on the
  **timeline**. Use `spanOf`/`spanLength` and `timelineTimeAt`/`sourceTimeAt`;
  never open-code the arithmetic.

`priority` is derived from track order, never authored. Layering is track order:
index 0 is the top row and the front of the composite.

### Several timelines: main and the templates it owns

"Expand Template" forks a placed template into the project as a
**composition** (`features/composition/`): a timeline of its own, edited in a
tab in the row above the timeline with the same timeline UI, named from the
clip as
`templateId: "project:<id>"`. Code says composition; the UI says Template.
Three rules keep the data flow from tangling:

- **The store holds the timeline being edited, and only that one, in
  `timeline`/`tracks`.** Every other timeline is in `parked`, which never holds
  the active id (`states/timelineWorkspace.ts`). Nothing that reads the store
  had to learn timelines exist. Anything that means **the project** (save,
  Auto Save, the dirty digest, recovery's emptiness, the export) reads
  `composition/projectSnapshot.ts`, never the store's live fields, and the
  render options' `previewSize` is the frame being edited, with main's kept in
  `mainFrame.ts` while a template shows.
- **One undo history for the whole project.** An entry snapshots every
  timeline by reference. An edit inside a composition re-fits the main clips
  naming it (`reconcile.ts`: shrink always, grow only into free space) in the
  same step; an undo keeps the tab unless the composition is gone. Switching is
  never a step, but it first commits anything previewed and uncommitted.
- **What follows a switch follows the switch, whatever caused it.** The store
  swaps each timeline's playhead, zoom and scroll and stops playback inside the
  switch's own `set` (`timelineStore.ts#viewSwap`: a write from inside its own
  listener reaches later listeners with the older state last). Everything in
  other stores (selection, frame size, preview pan, the playback loop) is
  `switchEffects.ts`, one subscriber. Gestures holding a document are ended
  first (`editor/gestureCancel.ts`) and check `switchEpoch`; work that awaits
  commits with `withCheckpointOn(timelineId)`, the timeline it started in.

Signals

Stars
818
Forks
79
Open issues
5
Contributors
6
Last commit (this folder)
Oct 21, 2026 (7 days ago)
Last release
Oct 16, 2026 (13 days ago)

From GitHub (fetched Oct 24, 2026).

Details

License
MIT
First seen
Oct 24, 2026
Last checked
Oct 24, 2026

More from cartesiancs

Cite this page

CLAUDE.md · cartesiancs/cartcut. Agent Mart. https://agentmart.dev/templates/cartesiancs-cartcut-claude-md (last updated 2026-10-24)