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
20 changes: 20 additions & 0 deletions content/docs/guide/ci-cd-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ one has its own section below.
| `lint.yml` | Lint | Push / PR to `main`, `develop`; manual | **Yes** — ESLint **errors** only |
| `changeset-guard.yml` | Changeset Bump Policy | PR / push touching `.changeset/**` | **Yes** |
| `performance-budget.yml` | Bundle Analysis | Push / PR touching `packages/**`, `apps/console/**`, `pnpm-lock.yaml` | **Yes** — the console entry gzip budget |
| `live-e2e.yml` | Live E2E (informational) | PR to `main`, `develop` (code paths); nightly cron `30 6 * * *`; manual | No — informational lane, `continue-on-error` |
| `labeler.yml` | Auto Label PRs | PR `opened`, `synchronize`, `reopened` | No |
| `dependabot-auto-merge.yml` | Dependabot Auto-merge | PR to `main`/`develop` authored by `dependabot[bot]` | No |
| `cross-repo-issue-closer.yml` | Cross-repo Issue Closer | PR `closed` (acts only when merged) | No — runs after merge |
Expand Down Expand Up @@ -144,6 +145,25 @@ reviewers; exceeding any of them turns no check red and blocks no merge:
> these tiers enforced, add the comparison to the workflow — do not describe it as
> enforced here.

## Live E2E (`live-e2e.yml`)

**Trigger:** PRs to `main` / `develop` (same code-path filter as `ci.yml` — docs-only and
changeset-only PRs skip it), a nightly cron (`30 6 * * *`) on `main`, and manual dispatch.

**Blocks a merge: no.** The job runs with `continue-on-error: true` by construction — a red run
is informational and never ejects a PR from the merge queue. Do not add it to required checks
(and do not remove `continue-on-error`) until the nightly record proves the lane stable; see the
header comment in the workflow file (#2835).

What it does: runs the allowlisted live specs (`pnpm test:e2e:live:ci` — screen-flow,
action-modal, master-detail) against a real `objectstack dev` backend booted from **published**
`@objectstack/*` packages serving the showcase app, catching the class of bug only a real
browser against a real backend can see. Failures still surface as a red step plus an uploaded
Playwright report and job summary.

Backend pins live in `e2e/live/ci/backend.env` and must match the `@objectstack/spec` version in
`pnpm-lock.yaml` — bump both in the same PR, or the run proves nothing.

## Link Checking (`check-links.yml`)

**Trigger:** Manual workflow dispatch (`workflow_dispatch`).
Expand Down
8 changes: 7 additions & 1 deletion packages/core/src/utils/dataset-format.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
* by both `@object-ui/plugin-dashboard` and `@object-ui/plugin-report`.
*/

import type { PercentScale } from '@objectstack/spec/data';

/**
* Column metadata the analytics server returns alongside the rows: a display
* `label` for both dimensions and measures, plus a measure's numeral `format`
Expand All @@ -41,8 +43,12 @@ export interface DatasetResultField {
* field's scale) and carried on the result column, so display never has to
* infer it from the value's magnitude — the inference that printed a ratio of
* exactly 1 as "1.0%" (#3136).
*
* Spec-owned since 17.0.0-rc.2 (`@objectstack/spec/data` exports the identical
* union); re-exported here so existing `@object-ui/core` consumers keep their
* import path.
*/
export type PercentScale = 'fraction' | 'whole';
export type { PercentScale } from '@objectstack/spec/data';

/**
* Scale a stored `percent`-field value to its DISPLAY magnitude.
Expand Down
86 changes: 86 additions & 0 deletions packages/types/src/__tests__/bulk-action-spec-parity.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/**
* ObjectUI
* Copyright (c) 2024-present ObjectStack Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* BulkAction* ↔ `@objectstack/spec` drift guard (objectui#3334).
*
* Spec 17.0.0-rc.2 absorbed the bulk-action vocabulary (`BulkActionDefSchema`
* et al. on `@objectstack/spec/ui`). `BulkActionOperation` is identical and is
* imported from the spec at its declaration in `objectql.ts`; the interfaces
* `BulkActionParam` / `BulkActionDef` stay objectui-side dialects (ALLOW
* entries in scripts/check-spec-symbol-derivation.mjs) because they type what
* the renderer EXECUTES, not what an author may write:
*
* - a def promoted at runtime from `bulkActions: ['<name>']` carries the
* source object action as `actionDef` (objectui#3002/#3139) — a resolution
* artifact the spec's strict schema rejects by design;
* - a promoted def's params come from the object action, so the renderer's
* param `type` is an open string with a widget-config catch-all, while the
* spec closes `type` over the authored FieldWidget enum.
*
* Each divergence is pinned here: if the spec starts ACCEPTING `actionDef`,
* or the two shapes converge some other way, a pin fails and the dialect must
* be reconciled (derive/re-export) instead of silently drifting.
*/

import { describe, it, expect } from 'vitest';
import {
BulkActionDefSchema as SpecBulkActionDefSchema,
BulkActionParamSchema as SpecBulkActionParamSchema,
BulkActionOperationSchema as SpecBulkActionOperationSchema,
} from '@objectstack/spec/ui';
import type { BulkActionDef, BulkActionOperation } from '../objectql';

const minimalDef = { name: 'close_won', operation: 'update' as const };

describe('BulkAction* ↔ spec parity (objectui#3334)', () => {
it('BulkActionOperation is the spec union, verbatim', () => {
// Compile-time: the re-exported type accepts exactly the spec's members …
const ops: BulkActionOperation[] = ['update', 'delete', 'custom'];
// … and runtime: the spec enum still holds exactly those members.
expect([...SpecBulkActionOperationSchema.options].sort()).toEqual(
[...ops].sort(),
);
});

it('spec def schema still REJECTS the runtime `actionDef` artifact', () => {
// The reason the local dialect exists. If the spec starts accepting
// `actionDef`, the shapes may have converged — reconcile, don't shadow.
const res = SpecBulkActionDefSchema.safeParse({
...minimalDef,
actionDef: { name: 'approve', type: 'script' },
});
expect(res.success).toBe(false);

// Sanity: the strictness is specific to the unknown key, not the base def.
expect(SpecBulkActionDefSchema.safeParse(minimalDef).success).toBe(true);

// The local dialect carries it (compile-time pin).
const local: BulkActionDef = {
...minimalDef,
actionDef: { name: 'approve', type: 'script' },
};
expect(local.actionDef).toBeDefined();
});

it('spec param `type` stays a closed enum the renderer deliberately widens', () => {
// A runtime-promoted param may carry an action-declared widget name the
// authored enum does not model; the local open `type: string` accepts it.
const res = SpecBulkActionParamSchema.safeParse({
name: 'assignee',
type: 'not_an_authored_widget',
});
expect(res.success).toBe(false);

// Shared vocabulary sanity: an authored widget type parses on both sides.
expect(
SpecBulkActionParamSchema.safeParse({ name: 'assignee', type: 'lookup' })
.success,
).toBe(true);
});
});
8 changes: 7 additions & 1 deletion packages/types/src/objectql.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
*/

import type { BaseSchema } from './base';
import type { BulkActionOperation } from '@objectstack/spec/ui';
import type { FormField } from './form';
// ListView type is now derived from the zod schema (issue #2231) — see ListViewSchema below.
import type { ListViewInferred } from './zod/objectql.zod.js';
Expand Down Expand Up @@ -309,8 +310,13 @@ export interface BulkActionParam {
* executor dispatches that action through the action runner: once per record by
* default, or once for the whole selection when the def opts into
* {@link BulkActionDef.execution} `'aggregate'` (objectui#3139).
*
* Spec-owned since 17.0.0-rc.2 (`@objectstack/spec/ui` exports the identical
* `'update' | 'delete' | 'custom'` union); re-exported so consumers keep this
* import path. (Imported at the top of this module — BulkActionDef below
* references it.)
*/
export type BulkActionOperation = 'update' | 'delete' | 'custom';
export type { BulkActionOperation };

/**
* Rich, schema-driven definition of a bulk action.
Expand Down
25 changes: 25 additions & 0 deletions scripts/check-spec-symbol-derivation.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,31 @@ const ALLOW = {
"the sanctioned set.",
issue: 4115,
},
"@object-ui/types:BulkActionParam": {
reason:
"Renderer-side dialect of the spec's authored param (objectui#3334). The spec's " +
"BulkActionParamSchema closes `type` over the FieldWidget enum — right for authored " +
"view metadata that `objectstack build` validates. This interface types what the " +
"BulkActionDialog RENDERS, which also includes defs promoted at runtime from object " +
"actions (`resolveBulkActions`, objectui#3002) whose param types are whatever the " +
"action declared — so `type` stays an open string and a catch-all index signature " +
"forwards widget-specific config (min/max/step/…) the way the field renderers expect. " +
"Divergence pinned by packages/types/src/__tests__/bulk-action-spec-parity.test.ts.",
issue: 3334,
},
"@object-ui/types:BulkActionDef": {
reason:
"Renderer-side dialect (objectui#3334): carries `actionDef` — the source object " +
"ActionDef attached at runtime when a `bulkActions: ['<name>']` entry is promoted " +
"(objectui#3002/#3139) — which the spec's STRICT BulkActionDefSchema rejects by design " +
"(it validates authored view metadata, and `actionDef` is a resolution artifact that " +
"must never be authored). `visible` also stays on the pre-normalization " +
"`string | { dialect?, source }` wire shape the action bridge forwards. Divergences " +
"pinned by packages/types/src/__tests__/bulk-action-spec-parity.test.ts; " +
"`BulkActionOperation` and the param/def spec keys are shared, and the operation union " +
"is imported from the spec at its declaration in objectql.ts.",
issue: 3334,
},
"@object-ui/auth:AuthProvider": {
reason:
"A REACT CONTEXT PROVIDER COMPONENT, not a type — `<AuthProvider authUrl=…>` is the " +
Expand Down
Loading