Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
12eedf1
feat(editor): draft gists locally, create new ones, and rename their …
soroushm Aug 2, 2026
05e7e03
fix(editor): open the startup document on the sandbox, and save under…
soroushm Aug 2, 2026
0809a0e
feat(editor): stream Claude into the document, and write from a gist …
soroushm Aug 2, 2026
bea8b09
refactor(CI): attribute a root change rather than run everything - cl…
soroushm Aug 5, 2026
6526e91
refactor(CI): hand the base files in rather than shelling out to git
soroushm Aug 5, 2026
a5d8075
test(editor): drop the fixed wait from the cancelled-run e2e
soroushm Aug 5, 2026
e5c9020
fix(editor): the stream, the rename and the drafts, as raised in review
soroushm Aug 5, 2026
1a2353d
fix(CI): skip a package the tree no longer has
soroushm Aug 5, 2026
1bc953a
test(editor): show the files panel starts over with each gist
soroushm Aug 5, 2026
d017458
refactor(CI): give each area a workflow that says what it validates -…
soroushm Aug 5, 2026
902dafc
docs(CI): name the caller each app workflow actually has
soroushm Aug 5, 2026
55b0a68
test(editor): give the close prompt a document whose save opens a dialog
soroushm Aug 5, 2026
0d749a2
fix(editor): stop an answer from a moment that has passed
soroushm Aug 5, 2026
05ee82d
fix(CI): read a marker without giving the engine a second way to match
soroushm Aug 5, 2026
3b5d586
chore(CI): pin our own action the way we pin everyone else's
soroushm Aug 5, 2026
0c44f9f
fix(editor): offer the gist Claude writes from to the keyboard too
soroushm Aug 5, 2026
bcbe769
fix(editor): follow the sandbox to the gist it became
soroushm Aug 5, 2026
390bfcc
fix(editor): publish a draft once, however many times the button is p…
soroushm Aug 5, 2026
eb7b2d8
docs(CI): name the job chain the split actually produced
soroushm Aug 5, 2026
dc0fc54
fix(editor): let a cancelled run stay cancelled
soroushm Aug 6, 2026
26f623a
refactor(editor): give the gist a name of its own to go by
soroushm Aug 6, 2026
d411681
fix(editor): call an undescribed gist what GitHub calls it
soroushm Aug 6, 2026
9254247
Merge branch 'main' into feat/editor
soroushm Aug 6, 2026
96769ac
test(editor): keep the evidence a CI-only e2e failure leaves behind
soroushm Aug 6, 2026
7f47743
fix(CD): tell the wrangler action which package manager it is in
soroushm Aug 6, 2026
83fb122
docs: introduce the editor among the apps on the front page
soroushm Aug 6, 2026
1ee790d
Merge remote-tracking branch 'origin/feat/editor'
soroushm Aug 6, 2026
ef791f7
fix(scripts): let a merge through the published-version guard
soroushm Aug 6, 2026
a79e816
refactor(scripts): read the merge marker off disk instead of asking git
soroushm Aug 6, 2026
78db4ab
fix(scripts): keep the version guard on what a merge itself edits
soroushm Aug 6, 2026
0e3e03c
chore(bench): enable persisted logs and traces in wrangler observability
soroushm Aug 6, 2026
af689e0
feat(editor): package the desktop app and auto-update from GitHub Rel…
soroushm Aug 6, 2026
f297841
fix(scripts): read a merge's parents as the list MERGE_HEAD is
soroushm Aug 6, 2026
4c90522
fix(CD): keep lifecycle scripts out of the editor release install
soroushm Aug 6, 2026
836478c
fix(CI): keep lifecycle scripts out of every workflow install
soroushm Aug 6, 2026
829d157
fix(bench): install the sandbox image's tsx without lifecycle scripts
soroushm Aug 6, 2026
683c536
feat(editor): autosave gist drafts and keep reload behind the unsaved…
soroushm Aug 7, 2026
f79a46f
fix(bench): pin the sandbox image's tsx exactly
soroushm Aug 7, 2026
a7da4c0
fix(editor): harden the reload guard and the release/CI workflows
soroushm Aug 7, 2026
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
26 changes: 19 additions & 7 deletions .claude/skills/ci-cd/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: GitHub Actions CI/CD conventions for this repo — the unified ci.yml (prepare → changes-gated lint/web/e2e/packages/worker → ci-ok), the action-pinning rule (first-party version tags, third-party SHAs), per-workspace Codecov flags with tokenless-OIDC uploads, the CI-environment approval gate (with env-scoped vars forwarded to environment-less jobs via job outputs), Cloudflare deploys via cloudflare/wrangler-action, and the standalone Chromatic workflow. Use when adding, editing, or debugging any workflow under .github/workflows/.
description: GitHub Actions CI/CD conventions for this repo — the CI entry workflow calling one workflow per area (prepare → lintpackages/worker/app → ci-ok, with app → web/editor), the action-pinning rule (version tags for `actions/*`, commit SHAs for everything else including our own org), per-workspace Codecov flags with tokenless-OIDC uploads, the CI-environment approval gate (with env-scoped vars forwarded to environment-less jobs via job outputs), Cloudflare deploys via cloudflare/wrangler-action, and the standalone Chromatic workflow. Use when adding, editing, or debugging any workflow under .github/workflows/.
paths: .github/workflows/**
---

Expand All @@ -12,34 +12,46 @@ Each workflow has a per-file deep-dive doc next to it (`ci.md`, `cd-*.md`, `chro
| File | Name | Trigger |
| ------------------- | ------------------------ | ---------------------------------------------------------------------------- |
| `ci.yml` | `Continuous Integration` | `push` to `main`, all `pull_request` |
| `ci-packages.yml` | CI · Packages | `workflow_call` from `ci.yml` |
| `ci-worker.yml` | CI · Workers | `workflow_call` from `ci.yml` |
| `ci-app.yml` | CI · Apps | `workflow_call` from `ci.yml` |
| `ci-web.yml` | CI · Web | `workflow_call` from `ci-app.yml` |
| `ci-editor.yml` | CI · Editor | `workflow_call` from `ci-app.yml` |
| `cd-web.yml` | Pages + Storybook deploy | `workflow_run` of CI (success, `main`) + dispatch |
| `cd-worker-api.yml` | Cloudflare Worker deploy | `workflow_run` of CI (success, `main`) + dispatch |
| `cd-packages.yml` | Publish Packages (npm) | manual `workflow_dispatch` only — see the `release-notes` skill |
| `cd-editor.yml` | CD · Editor | manual `workflow_dispatch` only — draft GitHub Release of the installers |
| `chromatic.yml` | Chromatic | `pull_request` + `push` to `main` + `workflow_dispatch` (main), non-blocking |
| `label-area.yml` | Label Affected Area | `issues: opened` |

One CI workflow for the whole monorepo; CD is separate and **gated on CI success** — never deploy on a raw `push`.
One CI entry workflow calling one per area; CD is separate and **gated on CI success** — never deploy on a raw `push`.

## Action pinning convention — the load-bearing rule

Pin every `uses:` by the action's **origin**. Getting this wrong fails review: CodeRabbit flags SHA-pinned first-party actions; SonarQube flags version-tagged third-party ones.
Pin every `uses:` by the action's **origin**. Getting this wrong fails review: CodeRabbit flags SHA-pinned `actions/*`; SonarQube flags anything else on a version tag.

- **First-party** — `actions/*` (checkout, setup-node, cache, upload-artifact, github-script) and own-org `soroush-tech/*` (bench-action) → **version tag**: `actions/checkout@v5`, `soroush-tech/bench-action@v1`.
- **Third-party** (anything else — `pnpm/action-setup`, `codecov/codecov-action`, `cloudflare/wrangler-action`, `chromaui/action`, `dorny/paths-filter`) → **commit SHA** + `# vX` comment.
- **GitHub's own** — `actions/*` (checkout, setup-node, cache, upload-artifact, github-script) → **version tag**: `actions/checkout@v5`.
- **Everything else, our own org included** (`soroush-tech/bench-action`, `pnpm/action-setup`, `codecov/codecov-action`, `cloudflare/wrangler-action`, `chromaui/action`, `dorny/paths-filter`) → **commit SHA** + `# vX` comment.

Own-org used to sit with `actions/*` on a tag. It does not any more: what SHA-pinning defends against is a tag being moved, and our own tags move like anyone's. The SHA is bumped when the action releases, which is the point — the upgrade is a reviewed line, not a silent one.

## CI job shape

`prepare` → `lint` → `web` / `e2e` / `packages` / `worker` → `ci-ok`.
`prepare` → `lint` → three **caller jobs** (`packages`, `worker`, `app`) → `ci-ok`. Each caller `uses:` an area workflow; `ci-app.yml` calls one workflow per app in turn, so adding an app never touches the entry file. Nesting is three of the four levels GitHub allows, and it stays one run with one `ci-ok`.

- **Detect once in `prepare`** (node version from `.nvmrc`, package manager, runner, changed areas), reuse via `needs.prepare.outputs.*`. Never hard-code the node version.
- Heavy jobs are **change-gated** (`dorny/paths-filter`, no Nx/Turbo) so a package-only PR stays cheap. Wire dependency edges **manually**: a consumed package must appear in the consumer's filter (`web` ← `packages/**`, `worker` ← `packages/schema/**`). Include `pnpm-lock.yaml`, `.nvmrc`, and the workflow file in every filter so infra changes run everything.
- **A file per area, so the gate can be narrower than everything.** Each workflow declares its scope on line 1 (`# ci:validates pkg__*`), read by `scripts/assemble-changes.mjs`; unmarked or unparseable means the whole workspace. A caller job cannot set `environment:`/`timeout-minutes:`/`runs-on:` (those belong to the inner jobs), and **`secrets: inherit` is mandatory, per hop** — naming an environment-scoped secret at the call site passes an empty string, and a middle layer that omits it starves the workflow below.
- **One job per shape, not per member.** Packages, workers and the editor's unit tier are the same job — install, `test:coverage`, upload the lcov — so packages are one matrix and workers another, both built from the tree in `scripts/assemble-changes.mjs`. **Adding a workspace member must need no edit to `ci.yml`**: if a new area needs a job, ask first whether it is really a different shape (`web` builds; `editor-e2e` drives Electron) or just another row.
- Heavy jobs are **change-gated** (`dorny/paths-filter`, no Nx/Turbo) so a package-only PR stays cheap. Dependency edges are **derived, never listed**: a member runs when it changed or when a package it declares as a `workspace:` dependency changed. Do not add a hand-written consumer list — it is a list to forget the day a dependency moves.
- **A workflow file validates what it runs**, and says so itself on line 1. `ci.yml` is `all`; a `cd-*`, Chromatic or the labeller is `nothing`, because CI never executes them. Same for `labs/*`, which has no job at all. The marker line takes tokens only — prose on the line below, or a stray `nothing` in it silently narrows the file to zero.
- **`web` is ubuntu-only** (build + unit/browser/storybook coverage). **`e2e` is the only multi-OS matrix** (one Playwright engine per native OS; macOS ≈10× cost → WebKit only) and **`needs: web`**, so a `web` failure skips it instead of re-running three OSes.
- **`ci-ok`** is the single branch-protection check: `if: always()`, fails only on a needed job's `failure`/`cancelled` (change-gated skips pass). **Add every new job to its `needs`.**

## Coverage → Codecov

- Each workspace emits `coverage/lcov.info` and uploads under its **own flag** (`codecov/codecov-action`, SHA-pinned); register each area as a `.codecov.yml` component. Vitest configs set `reporter: ['text', 'lcov']`; 100% is enforced in `vitest.config` (`thresholds: { 100: true }`), Codecov is reporting only.
- The **`web` flag is the single merged `test:coverage` pass** — that's the patch gate. The per-tier `unit`/`browser`/`storybook` flags run `all: true` and stay **informational** (don't gate on them — phantom-uncovered lines). `e2e` is the chromium-only page-coverage flag.
- A matrix row's flag is the **unscoped package name**, not its directory: two members are called `bench`, and their flags are `bench` (the package) and `bench-api` (the worker). The editor splits the same way as the web app: `editor` for the unit tier, `editor-e2e` for the Electron run.

## The `environment: CI` approval gate

Expand Down
14 changes: 14 additions & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ coverage:
# App/worker flags are hand-managed; the package flags below are generated.
- web
- e2e
- editor
- editor-e2e
- api
- bench-api
# gen:codecov-patch-flags start — regenerated by `pnpm gen:publish-options`; do not edit by hand
Expand Down Expand Up @@ -100,6 +102,14 @@ flag_management:
- name: e2e
paths:
- apps/web/
# The editor splits the same way: `editor` is its jsdom unit tier, `editor-e2e` the
# Playwright-Electron run, which covers the two entry files the unit tier excludes.
- name: editor
paths:
- apps/editor/
- name: editor-e2e
paths:
- apps/editor/

component_management:
default_rules:
Expand Down Expand Up @@ -159,6 +169,10 @@ component_management:
paths:
- packages/wrangler-tools/**
# gen:codecov-components end
- component_id: editor
name: editor
paths:
- apps/editor/**
- component_id: api
name: api
paths:
Expand Down
Loading