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
8 changes: 8 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,14 @@ vitest 调用被拒绝:从包目录跑 vitest 会静默跑错测试集 (objectui

Its "repository root" is `ui/`. The guard is `ui/scripts/vitest-invocation-guard.mjs`.

### UI CI

GitHub runs only the workflows in the repository root's
[workflow directory](./.github/workflows/), so a `ui/` change is checked by the same
`ci.yml` as the rest of the repository: its `Lint UI`, `Type Check UI`, `Test UI` and
`Build & E2E UI` jobs run when a pull request changes `ui/`. Read those jobs for what each one
runs; `ui/` keeps no CI guide of its own.

### UI examples

No directory under `ui/examples/` is a dev server: `hello-world` is a snippet to paste into
Expand Down
3,246 changes: 0 additions & 3,246 deletions ui/content/docs/guide/ci-cd-pipeline.md

This file was deleted.

2 changes: 1 addition & 1 deletion ui/content/docs/guide/deployment.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ For platforms that expect an HTTP 200 on `/`, the SPA `index.html` fallback alre

## Next Steps

- [CI/CD Pipeline](/docs/guide/ci-cd-pipeline) — Understand the automated build and release workflows
- [CI workflows](https://github.com/objectstack-ai/objectstack/tree/main/.github/workflows) — The repository-root workflows that build, test and release ObjectUI
- [Architecture Overview](/docs/guide/architecture) — How ObjectUI packages fit together
- [Quick Start](/docs/guide/quick-start) — Set up a new ObjectUI project from scratch
- [Theming](/docs/guide/theming) — Customize the look-and-feel before deploying
1 change: 0 additions & 1 deletion ui/content/docs/guide/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@
"user-state-persistence",
"objectos-integration",
"troubleshooting",
"ci-cd-pipeline",
"deployment",
"release-notes"
]
Expand Down
3 changes: 2 additions & 1 deletion ui/e2e/live/ci/backend.env
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
# OBJECTSTACK_VERSION — the published @objectstack/* npm version the backend
# runs on. Until objectui moved into the objectstack repository it had to
# match the @objectstack/spec version this repo's pnpm-lock.yaml resolved,
# and ci-cd-pipeline-doc.test.ts required the two equal (objectui#7689).
# and objectui's CI-guide pin test (deleted by objectstack#26058) required the two equal
# (objectui#7689).
# Since the import (objectstack#25803) the console builds against the
# workspace's own @objectstack/spec, a lockfile link with no version, so
# that comparison is retired and this value is the published backend the
Expand Down
4 changes: 2 additions & 2 deletions ui/e2e/live/saved-view-filter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import { test, expect } from '@playwright/test';
* only the server can answer the question the issue asked: does it accept a
* rule array in `$filter`? It does not. Measured against the published backend
* backend this lane pins — `OBJECTSTACK_VERSION` in `e2e/live/ci/backend.env`,
* held to the lockfile by `scripts/__tests__/ci-cd-pipeline-doc.test.ts`
* (objectui#7689), with the showcase-app commit derived from it at boot from
* once held to the lockfile by objectui's CI-guide pin test (objectui#7689; deleted by
* objectstack#26058), with the showcase-app commit derived from it at boot from
* the matching `@objectstack/cli` release tag (objectui#7964). That pin has
* moved since this measurement was first taken, which is why it is named by
* FILE and not by value: a version literal written here goes stale behind the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
*
* ## Why the consumer sweep is a blunt string match
*
* It follows the `scripts/__tests__/ci-cd-pipeline-doc.test.ts` pattern (#4170):
* It follows the pattern objectui's CI-guide pin test set (#4170; deleted by objectstack#26058):
* read the real files, compare them, pin the inverse direction too. A type-aware
* "is this identifier read anywhere" analysis would be more precise and much more
* fragile, and it would still miss the reachable path that has no static reference
Expand Down
2 changes: 1 addition & 1 deletion ui/scripts/__tests__/check-control-bytes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -505,7 +505,7 @@ describe('wiring — the gate is actually reachable and actually runs', () => {
// a markdown-only change anywhere under `ui/`.
expect(withoutComments(triggerBlock(ci))).not.toMatch(/^\s+paths(-ignore)?:/m);
const globs = uiFilterGlobs(ci);
const missed = ['README.md', 'content/docs/guide/ci-cd-pipeline.md', 'packages/core/README.md']
const missed = ['README.md', 'content/docs/guide/deployment.md', 'packages/core/README.md']
.map(repoPath)
.filter((file) => !globs.some((glob) => globMatches(glob, file)));
expect(missed, 'a markdown-only change under ui/ would skip Lint UI').toEqual([]);
Expand Down
6 changes: 0 additions & 6 deletions ui/scripts/__tests__/check-doc-example-shared-reader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -490,12 +490,6 @@ describe('check-doc-example-shared-reader — this repository', () => {
jobBlock(rootCi(), UI_JOBS.typeCheck),
'the gate must run in CI, next to the other source-parsing gates',
).toContain(`run: ${uiRun('check:doc-example-readers')}`);

// The page that inventories the gates is pinned by command (objectui#3653), so
// this would fail there too — asserted here as well because a reader looking
// for this gate looks at this file first.
const page = fs.readFileSync(path.join(repoRoot, 'content/docs/guide/ci-cd-pipeline.md'), 'utf8');
expect(page).toContain('check:doc-example-readers');
});
});

Expand Down
28 changes: 3 additions & 25 deletions ui/scripts/__tests__/check-doc-example-types.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -546,31 +546,9 @@ describe('wiring — the gate is reachable by name, and documented by command',
// case at the bottom keeps its `package.json` half and drops the workflow
// half for the same reason.

/**
* objectui#3653's discipline, applied by hand here: the page that inventories
* this repository's gates must name the command a contributor can be stopped
* by. `doc-snippet-types.yml` carries no `commandParity` unit in
* `ci-cd-pipeline-doc.test.ts` — verified when this landed — so this stands in
* for one rather than relying on a pin that does not cover the section.
*/
it('is documented on the page that inventories the gates, by command', () => {
const page = fs.readFileSync(
path.join(repoRoot, 'content/docs/guide/ci-cd-pipeline.md'),
'utf8',
);
expect(page, 'a contributor stopped by this gate must be able to find it').toContain(
'pnpm check:doc-examples',
);
expect(page).toContain(GATE);
// The by-command row, asserted as the whole row rather than by a regex that
// could drift onto the prose mentions elsewhere in the same section.
const row = page
.split('\n')
.filter((line) => line.startsWith('|') && line.includes('`pnpm check:doc-examples`'));
expect(row, 'exactly one by-command table row must name this gate').toHaveLength(1);
expect(row[0], 'the page must say the gate BLOCKS, not merely that it exists').toContain('**Yes**');
expect(row[0]).toContain(GATE);
});
// `is documented on the page that inventories the gates, by command` retired
// with objectstack#26944, which deleted that page (objectui's CI guide, which
// described workflows that run nowhere under the monorepo).

it('the root package.json alias invokes the gate', () => {
const pkg = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8')) as {
Expand Down
37 changes: 12 additions & 25 deletions ui/scripts/__tests__/check-doc-expression-carriage.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -856,28 +856,15 @@ describe('check-doc-expression-carriage: the real tree, and the posture', () =>
});
});

describe('check-doc-expression-carriage: the wiring', () => {
// `is actually run by ci.yml` retired with objectstack#25712. The census ran,
// report-only, in objectui ci.yml's `docs` job (`Build Docs`), and that file
// runs nowhere under the monorepo. The job is row 7 `Build Docs
// (@object-ui/site)` of the objectstack#25697 workflow roster, "kept,
// follow-up port": no root job runs the census until that port lands, and
// the port brings this pin back against its job (`helpers/root-ci.ts`).

it('is named on the page that documents what CI runs', () => {
const page = fs.readFileSync(path.join(ROOT, 'content/docs/guide/ci-cd-pipeline.md'), 'utf8');
expect(page).toContain(GATE);
// objectui#3653 pins the pairing by command; this asserts the page also says
// the posture, because a row that reads like a guardrail when the step blocks
// nothing is the objectui#3451 mistake.
const row = page.split('\n').find((line) => line.startsWith('|') && line.includes(GATE));
expect(row, 'the docs job row must name the gate').toBeDefined();
expect(row!.toLowerCase()).toContain('report-only');
// objectui#7878: the row named `content/docs/**` while the walk had widened.
// A row that understates a gate's reach is the same defect one level up from
// objectui#3451 — a page describing a guardrail that is not the one that runs.
expect(row!).toContain(DOCS_ROOT);
expect(row!).toContain(`${APP_DOCS.dir}/`);
for (const name of ROOT_PAGES) expect(row!).toContain(name);
});
});
// The wiring.
//
// `is actually run by ci.yml` retired with objectstack#25712. The census ran,
// report-only, in objectui ci.yml's `docs` job (`Build Docs`), and that file
// runs nowhere under the monorepo. The job is row 7 `Build Docs
// (@object-ui/site)` of the objectstack#25697 workflow roster, "kept,
// follow-up port": no root job runs the census until that port lands, and
// the port brings this pin back against its job (`helpers/root-ci.ts`).
//
// `is named on the page that documents what CI runs` retired with
// objectstack#26944, which deleted that page: it described objectui's
// workflows, and the census runs in none of the root ones yet.
2 changes: 1 addition & 1 deletion ui/scripts/__tests__/check-doc-fence-languages.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -519,7 +519,7 @@ describe('check-doc-fence-languages is wired, not merely present', () => {
// The root job runs on the `filter` job's `ui` globs, so those globs are the
// path filter that matters, and a docs-only change under `ui/` must match.
const globs = uiFilterGlobs(rootCi());
const missed = ['content/docs/guide/ci-cd-pipeline.md', 'packages/core/README.md']
const missed = ['content/docs/guide/deployment.md', 'packages/core/README.md']
.map(repoPath)
.filter((file) => !globs.some((glob) => globMatches(glob, file)));
expect(missed, 'a docs-only change under ui/ would skip Lint UI').toEqual([]);
Expand Down
7 changes: 3 additions & 4 deletions ui/scripts/__tests__/check-doc-json-fences.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -536,11 +536,10 @@ describe('wiring — the gate is reachable by name, and documented', () => {
// into the root `Lint UI` job and brings these pins back against that job, by
// path.

it('has a root script and is documented on the page that inventories the gates', () => {
// Its page half (`pnpm check:doc-json` named on the page that inventoried the
// gates) retired with objectstack#26944, which deleted that page.
it('has a root script', () => {
const manifest = JSON.parse(fs.readFileSync(path.join(repoRoot, 'package.json'), 'utf8'));
expect(manifest.scripts['check:doc-json']).toBe(`node ${GATE}`);
const page = fs.readFileSync(path.join(repoRoot, 'content/docs/guide/ci-cd-pipeline.md'), 'utf8');
expect(page).toContain('pnpm check:doc-json');
expect(page).toContain(GATE);
});
});
24 changes: 2 additions & 22 deletions ui/scripts/__tests__/check-doc-snippet-emitted-census.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -439,28 +439,8 @@ describe('wiring — a census nothing runs is not a census', () => {
).toBeLessThan(gate);
});

it('is written up on the page objectui#3653 pins, and declared report-only there', () => {
const doc = fs.readFileSync(path.join(repoRoot, 'content/docs/guide/ci-cd-pipeline.md'), 'utf8');
expect(doc).toContain('--emit-census');
const row = doc.split('\n').find((line) => line.includes('--emit-census'));
expect(row, 'the page must say what it runs').toBeDefined();
// The paragraph the command lives in, bounded by its own blank lines: a
// byte window would pass on a `report-only` belonging to another section.
const at = doc.indexOf('--emit-census');
const start = doc.lastIndexOf('\n\n', at);
const end = doc.indexOf('\n\n', at);
// Whitespace-normalised: this page hard-wraps, so a sentence that must be
// present is split across lines in the source and matches nothing verbatim.
const paragraph = doc
.slice(start, end === -1 ? doc.length : end)
.toLowerCase()
.replace(/\s+/g, ' ');
expect(
paragraph,
'a step the page does not declare report-only is a guardrail readers believe in',
).toContain('report-only');
expect(paragraph).toContain('exits 0 regardless of what it finds');
});
// `is written up on the page objectui#3653 pins, and declared report-only
// there` retired with objectstack#26944, which deleted that page.
});

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,7 @@ describe('wiring -- the gate is actually reachable and actually runs', () => {
const missed = [
'packages/core/src/index.ts',
'apps/site/source.config.ts',
'content/docs/guide/ci-cd-pipeline.md',
'content/docs/guide/deployment.md',
'scripts/check-docs-route-eager-closure.mjs',
]
.map(repoPath)
Expand Down
5 changes: 3 additions & 2 deletions ui/scripts/__tests__/check-governed-queue-guard.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,9 @@ describe('check-governed-queue-guard is wired, not merely present', () => {
it('this repository has written the context down as blocking', () => {
// `REQUIRED_CONTEXTS` is objectui's own answer to "which checks are
// blocking" (`scripts/dependabot-merge-gate.mjs`), and it is what
// `merge-queue-reporting.test.ts` derives the merge_group floor from. A
// guard outside it is a guard that floor cannot see.
// `merge-queue-reporting.test.ts` derived the merge_group floor from
// (retired by objectstack#26944 with the page it last pinned). A guard
// outside it is one a Dependabot merge is let past.
expect(REQUIRED_CONTEXTS).toContain(CHECK_CONTEXT_NAME);
});

Expand Down
6 changes: 0 additions & 6 deletions ui/scripts/__tests__/check-handler-key-read-sites.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1156,12 +1156,6 @@ describe('check-handler-key-read-sites — this repository', () => {
jobBlock(rootCi(), UI_JOBS.typeCheck),
'the gate must run in CI, next to the other source-parsing gates',
).toContain(`run: ${uiRun('check:handler-key-reads')}`);

// The page that inventories the gates is pinned by command (objectui#3653), so
// this would fail there too — asserted here as well because a reader looking
// for this gate looks at this file first.
const page = fs.readFileSync(path.join(repoRoot, 'content/docs/guide/ci-cd-pipeline.md'), 'utf8');
expect(page).toContain('check:handler-key-reads');
});

it('derives its arms from the mirrors, including the one the card is about', () => {
Expand Down
31 changes: 4 additions & 27 deletions ui/scripts/__tests__/check-new-cross-file-line-citations.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import { judge } from '../cross-file-line-citation-census.mjs';

const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../..');
const gatePath = path.join(repoRoot, 'scripts/check-new-cross-file-line-citations.mjs');
const docPath = path.join(repoRoot, 'content/docs/guide/ci-cd-pipeline.md');

/** Judges a hit against the real tree, the way the gate's own run does. */
function judgeAgainstTree(hit: { citedWritten: string; citedLine: number; file: string; anchors: string[] }) {
Expand Down Expand Up @@ -427,34 +426,12 @@ describe('the instrument does not count itself', () => {
* which runs nowhere under the monorepo: GitHub runs the root
* `.github/workflows/` alone, and no root workflow runs this gate. It is row 15
* of the objectstack#25697 workflow roster (→ sub-issue 4); a port brings these
* pins back against its root workflow, by path. The page and the classifier
* pins below read no workflow and stand.
* pins back against its root workflow, by path. The two page pins (`is written
* up on the page objectui#3653 pins, and declared report-only there` and `has a
* heading naming the workflow file`) retired with objectstack#26944, which
* deleted that page. The classifier pin below reads neither and stands.
*/
describe('wiring — a gate nothing runs is not a gate', () => {
it('is written up on the page objectui#3653 pins, and declared report-only there', () => {
const doc = fs.readFileSync(docPath, 'utf8');
expect(doc).toContain('node scripts/check-new-cross-file-line-citations.mjs');

const at = doc.indexOf('node scripts/check-new-cross-file-line-citations.mjs');
const start = doc.lastIndexOf('\n\n', at);
const end = doc.indexOf('\n\n', at);
// Whitespace-normalised: the page hard-wraps, so a sentence that must be
// present is split across lines in the source and matches nothing verbatim.
const around = doc
.slice(start, end === -1 ? doc.length : end + 400)
.toLowerCase()
.replace(/\s+/g, ' ');
expect(around, 'a step the page does not declare report-only is a guardrail readers believe in')
.toContain('report-only');
expect(around).toContain('exits 0 regardless of what it finds');
});

it('has a heading naming the workflow file, which the inventory pin also requires', () => {
const doc = fs.readFileSync(docPath, 'utf8');
const headings = doc.split('\n').filter((line) => /^#{1,6}\s/.test(line));
expect(headings.some((h) => h.includes('line-citation-gate.yml'))).toBe(true);
});

it('is classified by the Dependabot merge gate, with its reason', () => {
// An unclassified blocking check is one a Dependabot merge would be let
// past (objectui#6135); an unclassified NON-blocking one fails that gate's
Expand Down
13 changes: 8 additions & 5 deletions ui/scripts/__tests__/check-required-check-set.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -282,9 +282,9 @@ describe('check-required-check-set — the declaration cannot drift from the rep
/**
* `REQUIRED_CONTEXTS` (`scripts/dependabot-merge-gate.mjs`) is where this
* repository already writes down that a check is blocking and reports on
* every pull request; `content/docs/guide/ci-cd-pipeline.md` calls it "a
* human's answer" to which contexts may be required. The nine names in the
* live ruleset are a subset of it — measured 2026-09-14, 9 of 26 — so a
* every pull request: a human's answer to which contexts may be required.
* The nine names in the live ruleset are a subset of it — measured
* 2026-09-14, 9 of 26 — so a
* rename that updates that list and forgets this one fails here instead of
* quietly leaving the patrol watching a name nothing produces.
*/
Expand Down Expand Up @@ -354,8 +354,11 @@ describe('check-required-check-set — the wiring', () => {
* leg still proves the detector can fire before any zero here is read.
*/
describe('check-required-check-set — the prose points here instead of answering (#9502)', () => {
/** The files objectui#9502 repaired, less the workflow the note above retires. */
const REPAIRED = ['content/docs/guide/ci-cd-pipeline.md', 'scripts/dependabot-merge-gate.mjs'];
/**
* The files objectui#9502 repaired, less the workflow the note above retires
* and objectui's CI guide page, which objectstack#26944 deleted.
*/
const REPAIRED = ['scripts/dependabot-merge-gate.mjs'];

/**
* The claim being hunted: the ruleset cannot be READ from this repository.
Expand Down
5 changes: 3 additions & 2 deletions ui/scripts/__tests__/check-shell-escape-residue.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,8 +705,9 @@ describe('wiring — the gate is reachable by name, and classified', () => {
});

it('is classified as a required context — the merge_group floor DERIVES from that list', () => {
// objectui#6160 / PR #6187: `merge-queue-reporting.test.ts` reads
// `REQUIRED_CONTEXTS` to decide which workflows must subscribe the queue,
// objectui#6160 / PR #6187: `merge-queue-reporting.test.ts`, retired by
// objectstack#26944, read `REQUIRED_CONTEXTS` to decide which workflows must
// subscribe the queue,
// and `dependabot-merge-gate.mjs` would let a Dependabot merge past an
// unclassified blocking check (objectui#6135).
// Since objectui#12214 the check reports as a step of the ONE `Repo Gates`
Expand Down
10 changes: 2 additions & 8 deletions ui/scripts/__tests__/check-skill-eval-tokens.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -390,12 +390,6 @@ describe('wiring — the gate is reachable by name, classified and documented',
expect(gate).toContain("'Repo Gates'");
});

it('has a section on the CI page, named by heading', () => {
// `ci-cd-pipeline-doc.test.ts` enforces this repo-wide; restated here
// because objectui#3212's lesson is that the omission happens at the moment
// the workflow is added, not later.
const doc = fs.readFileSync(path.join(repoRoot, 'content/docs/guide/ci-cd-pipeline.md'), 'utf8');
const headings = doc.split('\n').filter((line) => /^#{1,6}\s/.test(line));
expect(headings.some((h) => h.includes('repo-gates.yml'))).toBe(true);
});
// `has a section on the CI page, named by heading` retired with
// objectstack#26944, which deleted that page.
});
Loading
Loading