Skip to content
Open
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
24 changes: 0 additions & 24 deletions .github/workflows/actions-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,8 @@ permissions:
contents: read

jobs:
changes:
uses: ./.github/workflows/ci-changes.yml

test:
name: Build action suite${{ matrix.node == 26 && ' (Node 26)' || '' }}
needs: changes
if: ${{ needs.changes.outputs.build_action == 'true' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand All @@ -39,22 +34,3 @@ jobs:
run: cp actions/build/test/fixture/package.json actions/build/test/fixture/package-lock.json actions/build/test/fixture/fixture-build.mjs .
- uses: ./actions/build
if: ${{ matrix.node == 24 }}

result:
name: Replica action tests
needs: [changes, test]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Require the selected build-action suite to pass
env:
SELECTION_RESULT: ${{ needs.changes.result }}
SELECTED: ${{ needs.changes.outputs.build_action }}
TEST_RESULT: ${{ needs.test.result }}
run: |
test "$SELECTION_RESULT" = success
case "$SELECTED" in
true) test "$TEST_RESULT" = success ;;
false) test "$TEST_RESULT" = skipped ;;
*) echo "Invalid build-action suite selection" >&2; exit 1 ;;
esac
40 changes: 0 additions & 40 deletions .github/workflows/ci-changes.yml

This file was deleted.

48 changes: 18 additions & 30 deletions .github/workflows/hub-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,19 +13,28 @@ defaults:
working-directory: hub

jobs:
changes:
uses: ./.github/workflows/ci-changes.yml

check:
name: Hub ${{ matrix.command }}${{ matrix.node == 26 && ' (Node 26)' || '' }}
needs: changes
if: ${{ needs.changes.outputs.hub == 'true' }}
name: Hub ${{ matrix.command == 'test' && 'unit tests' || matrix.command == 'test:database' && 'database tests' || matrix.command }}${{ matrix.node == 26 && ' (Node 26)' || '' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node: [24, 26]
command: [lint, typecheck, test, build, storybook:build, "db:validate"]
command: [lint, typecheck, test, build, storybook:build, "db:validate", "test:database"]
services:
postgres:
image: ${{ matrix.command == 'test:database' && 'postgres:18.6-bookworm' || '' }}
env:
POSTGRES_USER: replica
POSTGRES_PASSWORD: replica
POSTGRES_DB: postgres
ports:
- 5432:5432
options: >-
--health-cmd "pg_isready -h 127.0.0.1 -U replica -d postgres"
--health-interval 5s
--health-timeout 5s
--health-retries 10
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
Expand All @@ -35,11 +44,11 @@ jobs:
cache-dependency-path: hub/package-lock.json
- run: npm ci --engine-strict
- run: npm run ${{ matrix.command }}
env:
TEST_DATABASE_URL: ${{ matrix.command == 'test:database' && 'postgresql://replica:replica@127.0.0.1:5432/postgres' || '' }}

integration:
name: Controls and extension tests${{ matrix.node == 26 && ' (Node 26)' || '' }}
needs: changes
if: ${{ needs.changes.outputs.hub == 'true' }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
Expand Down Expand Up @@ -67,24 +76,3 @@ jobs:
- run: npm run controls:assets:check
- run: npm run controls:schema:check
- run: npm run extension:assets:check

result:
name: Frontend tests
needs: [changes, check, integration]
if: ${{ always() }}
runs-on: ubuntu-latest
steps:
- name: Require the selected Hub suite to pass
working-directory: .
env:
SELECTION_RESULT: ${{ needs.changes.result }}
SELECTED: ${{ needs.changes.outputs.hub }}
CHECK_RESULT: ${{ needs.check.result }}
INTEGRATION_RESULT: ${{ needs.integration.result }}
run: |
test "$SELECTION_RESULT" = success
case "$SELECTED" in
true) test "$CHECK_RESULT" = success && test "$INTEGRATION_RESULT" = success ;;
false) test "$CHECK_RESULT" = skipped && test "$INTEGRATION_RESULT" = skipped ;;
*) echo "Invalid Hub suite selection" >&2; exit 1 ;;
esac
6 changes: 0 additions & 6 deletions .github/workflows/learning-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,8 @@ name: Learning CI

on:
pull_request:
paths:
- ".github/workflows/learning-ci.yml"
- "packages/learning/**"
push:
branches: [main]
paths:
- ".github/workflows/learning-ci.yml"
- "packages/learning/**"

permissions:
contents: read
Expand Down
27 changes: 27 additions & 0 deletions .github/workflows/repository-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
name: Repository CI

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

jobs:
check:
name: Repository ${{ matrix.command }}
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
command: [typecheck, test, "check:workflows"]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
with:
node-version: 24
cache: npm
cache-dependency-path: scripts/package-lock.json
- run: npm ci --prefix scripts --ignore-scripts --engine-strict
- run: npm run ${{ matrix.command }} --prefix scripts
14 changes: 0 additions & 14 deletions .github/workflows/starter-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,8 @@ name: Starter CI

on:
pull_request:
paths:
- ".github/workflows/starter-ci.yml"
- "starter/**"
- "scripts/create-prototype-repo.sh"
- "scripts/create-prototype-repo.test.mjs"
- "scripts/setup-replica-plugin.test.mjs"
- "scripts/install-starter.sh"
push:
branches: [main]
paths:
- ".github/workflows/starter-ci.yml"
- "starter/**"
- "scripts/create-prototype-repo.sh"
- "scripts/create-prototype-repo.test.mjs"
- "scripts/setup-replica-plugin.test.mjs"
- "scripts/install-starter.sh"

permissions:
contents: read
Expand Down
3 changes: 2 additions & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ npm run build
npm run storybook:build
```

Run `npm run test:database` with the [Hub development database](hub/docs/development.md) for server cases that save records. It prepares separate databases per Vitest worker; ordinary `npm test` remains database-free.

Useful focused commands include:

| Goal | Command |
Expand All @@ -124,7 +126,6 @@ Repository workflow and dependency checks have their own small npm project:
npm ci --prefix scripts --ignore-scripts --engine-strict
npm run typecheck --prefix scripts
npm test --prefix scripts
node --test scripts/select-ci-suites.test.mjs
node scripts/check-ci-workflows/index.ts
node scripts/check-dependency-age/index.ts
```
Expand Down
24 changes: 6 additions & 18 deletions docs/testing-and-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,31 +40,19 @@ Project commands define the check. GitHub Actions should prepare the required en

When a command gains a dependency or build prerequisite, update its local setup and affected workflow in the same change. A green local suite does not prove that CI installed the same dependencies, so inspect both paths.

### Select affected CI suites
### Run independent CI checks

Hub and build-action workflows start on every pull request, but run their expensive jobs only when the change can affect that suite. Pushes to `main` run both complete suites. Local project commands still run their complete checks; selection changes when CI calls them, not what they do. Dependency-age checks remain unconditional, and Starter and Learning CI retain their own path filters.
Hub, build-action, Starter, Learning, and repository-tooling workflows run on every pull request and push to `main`. Do not add changed-path filters, suite selectors, or aggregate result gates. Checks run independently, and branch protection should require their individual check names.

The [shared selection workflow](../.github/workflows/ci-changes.yml) calls [the selector](../scripts/select-ci-suites.mjs). For pull requests, it compares the merge base with the PR head using Git rather than the size-limited changed-files API. Deletions and both sides of renames count as changes.
Hub reports lint, types, unit tests, database tests, the frontend/API build, Storybook, and Controls/extension checks separately. Only the database test command starts PostgreSQL. The new Hub has no production-image or release jobs.

| Changed area | Selected suites |
| --- | --- |
| Hub code and configuration | Hub |
| Shared Controls runtime, schema, fixtures, and capture adapter under `hub/app/javascript/utils/prototype-controls/` | Hub and build action |
| `actions/build/` | Hub and build action; Controls and extension integration use its pinned browser |
| Copilot extension and plugin catalog | Hub |
| `packages/learning/` | Hub; its shared lint and retained integration consume the package |
| `starter/` | Hub; retained Storybook fixtures and Controls checks consume starter files. Starter CI still runs separately |
| Existing installer scripts | Neither; Starter CI covers them |
| Root contributor documents, shared and Hub guides, skills, and documentation assets | Neither |
| Workflow changes, selector changes, and unclassified paths | Both |
When replacing the old aggregate gates, maintainers must replace `Frontend tests` and `Replica action tests` in any branch rules with the individual checks they require. Workflow changes do not update repository rules automatically.

The `Frontend tests` and `Replica action tests` result jobs always run, so skipping an entire workflow cannot leave these checks pending. They accept skipped work only when the selector succeeded and said it was unnecessary. Hub's result covers lint, types, Vitest, the frontend/API build, Storybook, and Controls/extension checks on every supported Node major. A failed selector, invalid output, or failed selected check makes the result fail. The new Hub has no production-image, database, or release jobs; branch protection must not require removed jobs.

When adding a shared dependency or a new project, update the selector and its behavioral tests together. Unknown paths deliberately run both suites until their dependencies are known. Install tooling with `npm ci --prefix scripts --ignore-scripts --engine-strict`, then run `node --test scripts/select-ci-suites.test.mjs`, `npm run typecheck --prefix scripts`, `npm test --prefix scripts`, and `node scripts/check-ci-workflows/index.ts` from the repository root. Keep the TypeScript checks in function directories with colocated Vitest tests. The workflow checker reads the result-check scripts and tests them with success, failure, cancellation, and missing outputs. These checks run before CI selects suites. Check changed workflows with `actionlint` when available. Actual job skipping and check reporting still need verification on GitHub after publishing.
The [repository workflow](../.github/workflows/repository-ci.yml) runs tooling type checks, tests, and workflow checks independently. Locally, install tooling with `npm ci --prefix scripts --ignore-scripts --engine-strict`, then run `npm run typecheck --prefix scripts`, `npm test --prefix scripts`, and `node scripts/check-ci-workflows/index.ts`. The workflow checker rejects aggregate gates, conditional jobs, path filters, and missing required commands. Keep TypeScript checks in function directories with colocated Vitest tests. Check changed workflows with `actionlint` when available. Actual check reporting still needs verification on GitHub after publishing.

Node-dependent jobs in Hub, Starter, build-action, and Learning CI use a Node 24 and 26 matrix rather than a separate compatibility workflow. Each check adds a `(Node 26)` suffix on Node 26. A failure does not cancel the other version. npm installation enforces dependency engines, and `scripts/check-ci-workflows/index.ts` checks the supported ranges, matching lockfile metadata, and matrix wiring. These checks track the supported majors, not their exact minimum patch releases.

CI selection and dependency-age tooling run on Node 24. The build-action suite tests its JavaScript and browser capture on both Node versions, but runs the composite-action smoke test only on Node 24: the composite action pins that runtime itself and uploads a single named artifact.
Repository and dependency-age tooling run on Node 24. The build-action suite tests its JavaScript and browser capture on both Node versions, but runs the composite-action smoke test only on Node 24: the composite action pins that runtime itself and uploads a single named artifact.

## Update dependencies

Expand Down
1 change: 1 addition & 0 deletions hub/.devcontainer/compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ services:
- ../..:/workspaces/replica:cached
environment:
DATABASE_URL: postgresql://replica:replica@db:5432/replica
TEST_DATABASE_URL: postgresql://replica:replica@db:5432/postgres
depends_on:
db:
condition: service_healthy
Expand Down
3 changes: 3 additions & 0 deletions hub/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,6 @@ PORT=3001

# Local development only. The devcontainer supplies its own database hostname.
DATABASE_URL=postgresql://replica:replica@127.0.0.1:5432/replica

# Only the database test project requires this URL and CREATEDB permission.
TEST_DATABASE_URL=postgresql://replica:replica@127.0.0.1:5432/postgres
2 changes: 2 additions & 0 deletions hub/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Hub is being rebuilt in TypeScript. The app starts locally, but its product feat

Run Node commands from `hub/`, locally or inside the devcontainer. PostgreSQL and Prisma are configured without product models. Agree on the design before adding models, sign-in, deployment, or product API routes.

`npm test` is database-free. Use `npm run test:database` for `.db.test.ts` server cases, with the worker-specific databases and shared reset lifecycle described in [testing](docs/testing-and-operations.md).

Keep the top-level `HubThemeProvider` around Hub and Storybook content. It synchronizes the resolved light/dark mode to `<html data-color-mode>` so portals inherit Brand and `--hub-*` tokens. Do not add a fixed light theme to an overlay.

Preserve the shared canvas and Controls source paths under `app/javascript/`; the prototype extension and build action consume them independently of the Hub API.
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
import { fireEvent, render, screen, waitFor, within } from "@testing-library/react"
import { afterEach, describe, expect, it, vi } from "vitest"
import { describe, expect, it, vi } from "vitest"

import { HubThemeProvider } from "@hooks/use-hub-theme"
import story from "./stories"

afterEach(() => {
vi.unstubAllGlobals()
vi.restoreAllMocks()
})

describe("PublicationDemo()", () => {
it("shows the updated comments in the complete canvas sidebar without contacting Hub", async () => {
const externalFetch = vi.fn(() => Promise.reject(new Error("Unexpected external request.")))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// @vitest-environment jsdom
import { afterEach, expect, it, vi } from "vitest"
import { beforeEach, expect, it, onTestFinished, vi } from "vitest"

import { getControlsRuntime } from "@utils/prototype-controls/controls.mjs"
import { installControlsBridge } from "./controls"
Expand All @@ -23,13 +23,12 @@ const declaration = {
],
} as const

afterEach(() => {
dispose?.()
dispose = undefined
delete globals[runtimeKey]
vi.restoreAllMocks()
vi.unstubAllGlobals()
window.history.replaceState(null, "", "/")
beforeEach(() => {
onTestFinished(() => {
dispose?.()
dispose = undefined
delete globals[runtimeKey]
})
})

function message(data: object, messageOrigin = origin, source: MessageEventSource = window.parent) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,17 @@
// @vitest-environment jsdom
import { afterEach, beforeEach, expect, it, vi } from "vitest"
import { beforeEach, expect, it, onTestFinished, vi } from "vitest"

import { REVIEW_ANCHOR_SCHEMA } from "@utils/review-anchor/schemas"

import { installBridge } from "."

beforeEach(() => installBridge("http://127.0.0.1:1234"))
beforeEach(() => {
onTestFinished(() => {
const bridgeWindow = window as Window & { [installed]?: () => void }
bridgeWindow[installed]?.()
})
installBridge("http://127.0.0.1:1234")
})

const TEXT_ENCODER = new TextEncoder()
const installed = Symbol.for("replica.canvas.bridge.v1")
Expand All @@ -18,16 +24,6 @@ function boundedText(maximumBytes: number, character: string, remainingBytes: nu
}
}

afterEach(() => {
const bridgeWindow = window as Window & { [installed]?: () => void }
bridgeWindow[installed]?.()
window.getSelection()?.removeAllRanges()
document.body.innerHTML = ""
document.body.style.position = ""
vi.restoreAllMocks()
vi.unstubAllGlobals()
})

it.each(["static", "fixed", "sticky"])(
"updates a %s-ancestor pin on scroll instead of waiting for polling",
async (position) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { fireEvent, render, screen, waitFor } from "@testing-library/react"
import { afterEach, describe, expect, it, vi } from "vitest"
import { describe, expect, it, vi } from "vitest"

import { CopyableTextInput } from "."
import styles from "./styles.css?inline"
Expand All @@ -12,8 +12,6 @@ const props = {
copyErrorLabel: "Copy failed",
}

afterEach(() => vi.unstubAllGlobals())

describe("CopyableTextInput()", () => {
it("fades long URLs into an opaque themed button without changing compact controls", () => {
const { rerender } = render(<CopyableTextInput {...props} />)
Expand Down
Loading