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
4 changes: 1 addition & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,4 @@ testenv/
experiments
experiments/*
.codex-logs/
.codex-dev-web.log
.codex-dev-web-*.stdout.log
.codex-dev-web-*.stderr.log
.codex-dev-web*.log
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,35 @@ test.describe('Generator Schema Editing', () => {
expectNoPageErrors(pageErrors);
});

test('tabs through schema row fields before row action buttons', async ({ page }) => {
const { generatorPage, pageErrors } = await openGenerator(page);

await generatorPage.schema.setTextMode(false);
const row = generatorPage.schema.row(0);
const nameInput = row.locator('input[data-field="name"]');
const sourceTypeSelect = row.locator('select[data-field="sourceType"]');
const helpLink = row.locator('[data-field="faker-doc-link"]');
const valueInput = row.locator('input[data-field="value"]');
const dragButton = row.locator('[data-action="drag"]');

await nameInput.focus();
await expect(nameInput).toBeFocused();

await page.keyboard.press('Tab');
await expect(sourceTypeSelect).toBeFocused();

await page.keyboard.press('Tab');
await expect(helpLink).toBeFocused();

await page.keyboard.press('Tab');
await expect(valueInput).toBeFocused();

await page.keyboard.press('Tab');
await expect(dragButton).toBeFocused();

expectNoPageErrors(pageErrors);
});

test('can edit as schema then edit as text', async ({ page }) => {
const { generatorPage, pageErrors } = await openGenerator(page);

Expand Down
39 changes: 39 additions & 0 deletions apps/web/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -2095,10 +2095,47 @@ body.theme-dark .generator-status-text[data-severity='info'] {
}

.shared-schema-row-actions {
grid-column: 1;
grid-row: 1;
Comment on lines 2097 to +2099

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Reset row placement in tablet layout

At widths 481–980px, the later @media (max-width: 980px) rules only change grid-column for the row actions and name input and do not reset grid-row. Because this new base rule pins the actions to row 1 while the new name-input rule also pins the textbox to row 1, the action buttons and column-name field overlap on tablet-width layouts; add responsive grid-row overrides while keeping the desktop left-action column.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented in aa7450b1.

The tablet layout now explicitly places the shared schema row actions on grid row 1 and the column-name input on grid row 2 inside the max-width: 980px rules, so the desktop left-action-column placement does not carry an overlapping row assignment into tablet widths.

Verified with the targeted generator schema-edit Playwright spec, pnpm run verify:ui, pnpm run verify:local, and the pre-push verification hook.

display: flex;
gap: 0.2rem;
}

.shared-schema-row > input[data-field='name'] {
grid-column: 2;
grid-row: 1;
}

.shared-schema-row > select[data-field='sourceType'] {
grid-column: 3;
grid-row: 1;
}

.shared-schema-row > .shared-schema-command-picker-control {
grid-column: 4;
grid-row: 1;
}

.shared-schema-row-command > .shared-schema-help-link {
grid-column: 5;
grid-row: 1;
}

.shared-schema-row-value > .shared-schema-help-link {
grid-column: 4;
grid-row: 1;
}

.shared-schema-row > .shared-schema-params-control {
grid-column: 6;
grid-row: 1;
}

.shared-schema-row > input[data-field='value'] {
grid-column: 5;
grid-row: 1;
}

.shared-schema-drag-handle {
cursor: grab;
}
Expand Down Expand Up @@ -2279,9 +2316,11 @@ body.theme-dark .shared-schema-row-validation {
}
.shared-schema-row-actions {
grid-column: 1 / span 3;
grid-row: 1;
}
.shared-schema-row > input[data-field='name'] {
grid-column: 1 / span 3;
grid-row: 2;
}
.shared-schema-row > select[data-field='sourceType'] {
grid-column: 1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,17 @@
slug: combinatorial-grid-workflows
authors: [alan]
tags: [release, feature, combinatorial, schema, import, export, ux]
tags: [release, feature, combinatorial, schema, import, export, ux, faker, api, cli, mcp]
date: 2026-06-12T10:00
---

The next release is centered on one theme: faster paths from existing data to realistic, constrained, exportable test sets.

This release adds broader combinatorial generation, schema authoring improvements, better import/export controls, and a few high-value grid usability upgrades.
This release adds broader combinatorial generation, schema authoring improvements, safe-by-default Faker helper controls across interfaces, better import/export controls, and a few high-value grid usability upgrades.

<!-- truncate -->

## 1. Auto-increment timestamps for deterministic event streams

You can now generate timestamps that move forward one row at a time instead of relying on purely random dates.

Example:

```text
CreatedAt
autoIncrement.timestamp(start="2026-06-12T12:39:23Z", step=1, type="seconds")
```

That produces:

- row 1: `2026-06-12T12:39:23Z`
- row 2: `2026-06-12T12:39:24Z`
- row 3: `2026-06-12T12:39:25Z`

This is useful for audit logs, event streams, ordered API records, and any test data where time should progress predictably across generated rows.

Docs:

- [autoIncrement Domain](/docs/test-data/domain/autoIncrement)

## 2. N-wise combinatorial generation, not just pairwise
## 1. N-wise combinatorial generation, not just pairwise

The biggest addition is that combinatorial generation now goes beyond pairwise.

Expand Down Expand Up @@ -63,7 +41,7 @@ Docs:

![N-wise combinations dialog](/img/release-198/n-wise-generation.png)

## 3. Schema constraints with PICT-style `IF ... THEN ...`
## 2. Schema constraints with PICT-style `IF ... THEN ...`

Schema constraints make generated combinations more realistic by filtering out invalid rows.

Expand Down Expand Up @@ -92,7 +70,7 @@ Docs:

- [Schema Definition](/docs/test-data/Schema-Definition)

## 4. Grid to Enum Schema for turning existing tables into generators
## 3. Grid to Enum Schema for turning existing tables into generators

If you already have representative data in the main grid, you can now turn that grid into an enum schema automatically.

Expand Down Expand Up @@ -124,32 +102,9 @@ Docs:

![Grid to enum schema in the app](/img/release-198/grid-to-enum-schema.png)

## 5. Constraint-aware auto-increment sequences for generated identifiers

Schemas can now generate sequential IDs through the domain model with `autoIncrement.sequence`.

Example:

```text
Filename
autoIncrement.sequence(start=1, step=5, prefix="filename", suffix=".txt", zeropadding=3)
```

Generated values:

```text
filename001.txt
filename0006.txt
filename011.txt
```

This is especially useful for ticket IDs, filenames, and human-readable references because the sequence only advances when a row is accepted. If a generated row is rejected by constraints and retried, the skipped attempt does not consume the next number.

Docs:

- [Auto Increment Sequences](/docs/test-data/auto-increment-sequences)

## 6. PICT-style inline enum definitions such as `Name: values`
## 4. PICT-style inline enum definitions such as `Name: values`

Schema text now fits more naturally with compact PICT-style authoring.

Expand All @@ -174,7 +129,8 @@ Docs:

- [Schema Definition](/docs/test-data/Schema-Definition)

## 7. Import trimming controls for cleaner amend and import workflows

## 5. Import trimming controls for cleaner amend and import workflows

Imported files and clipboard data can now be normalized during import.

Expand All @@ -197,7 +153,7 @@ Docs:

![Import trim settings](/img/release-198/import-trim-settings.png)

## 8. File export settings for line endings and BOM
## 6. File export settings for line endings and BOM

Downloads now support file transport settings without changing the preview text shown in the browser.

Expand All @@ -214,7 +170,7 @@ Docs:

![Download encoding settings](/img/release-198/export-encoding-settings.png)

## 9. Right-click context menu in the main data grid
## 7. Right-click context menu in the main data grid

The editable grid now has a right-click context menu for common grid actions.

Expand All @@ -224,7 +180,7 @@ Docs:

- [Data Grid Editable](/docs/test-data/data-grid-editable)

## 10. Always-visible total row counts in the data grid
## 8. Always-visible total row counts in the data grid

The main grid now shows total row counts, and filtered views also show how many rows remain visible.

Expand All @@ -251,7 +207,8 @@ Taken together, these features make the tool better at moving through the whole
1. start with imported or hand-edited data
2. convert it into a schema
3. add constraints
4. generate the right amount of combinatorial coverage
5. export in the format and file encoding you actually need
4. enable unsafe Faker helper expressions only when the schema is trusted
5. generate the right amount of combinatorial coverage
6. export in the format and file encoding you actually need

The release adds more generation power, reduces setup time, and cleanup time around that generation.
89 changes: 89 additions & 0 deletions docs-src/blog/2026-06-30-increments-and-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
---
slug: auto-increments-helpers
authors: [alan]
tags: [release, feature]
date: 2026-06-30T10:00
---
Comment thread
coderabbitai[bot] marked this conversation as resolved.

This release expands the commands available in the domain model, allows more helper functions in the UI and has general UI improvements.

<!-- truncate -->

## 1. Auto-increment timestamps for deterministic event streams

You can now generate timestamps that move forward one row at a time instead of relying on purely random dates.

Example:

```text
CreatedAt
autoIncrement.timestamp(start="2026-06-12T12:39:23Z", step=1, type="seconds")
```

That produces:

- row 1: `2026-06-12T12:39:23Z`
- row 2: `2026-06-12T12:39:24Z`
- row 3: `2026-06-12T12:39:25Z`

This is useful for audit logs, event streams, ordered API records, and any test data where time should progress predictably across generated rows.

Docs:

- [autoIncrement Domain](/docs/test-data/domain/autoIncrement)


## 2. Constraint-aware auto-increment sequences for generated identifiers

Schemas can now generate sequential IDs through the domain model with `autoIncrement.sequence`.

Example:

```text
Filename
autoIncrement.sequence(start=1, step=5, prefix="filename", suffix=".txt", zeropadding=3)
```

Generated values:

```text
filename001.txt
filename006.txt
filename011.txt
```
Comment thread
coderabbitai[bot] marked this conversation as resolved.

This is especially useful for ticket IDs, filenames, and human-readable references because the sequence only advances when a row is accepted. If a generated row is rejected by constraints and retried, the skipped attempt does not consume the next number.

Docs:

- [Auto Increment Sequences](/docs/test-data/auto-increment-sequences)

## 3. Allow unsafe Faker helpers from the UI when you trust the schema

Faker helper commands stay safe by default. Simple literal helper calls continue to work normally, but expression-style helper arguments that execute callback-shaped schema text require an explicit opt-in.

That opt-in is now available in the Web UI as **allow unsafe faker** in the Test Data Settings dialog behind the cog.

Example unsafe helper:

```text
Names
helpers.multiple(() => this.person.firstName(), { count: 3 })
```

Use this only for schemas you trust. The browser setting now matches the other integration surfaces:

- Web UI: enable **allow unsafe faker** from the Test Data Settings cog.
- REST API: pass `unsafeFakerExpressions: true` in supported generation request bodies.
- CLI: pass `--unsafe-faker-expressions`.
- MCP: pass `unsafeFakerExpressions: true` in the generation tool arguments.

Docs:

- [Faker Helpers](/docs/test-data/faker/helpers)
- [Web UI](/docs/interfaces-and-deployment/web-ui)
- [REST API](/docs/interfaces-and-deployment/rest-api)
- [MCP](/docs/interfaces-and-deployment/mcp)
- [CLI](/docs/interfaces-and-deployment/cli-node-and-bun)


Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ function captureActiveFieldState(documentObj) {
const fieldName = activeElement?.getAttribute?.('data-field');
const actionName = activeElement?.getAttribute?.('data-action');
const rowId = activeElement?.closest?.(SHARED_SCHEMA_ROW_SELECTOR)?.getAttribute?.('data-row-id');
if (!rowId || (!fieldName && actionName !== 'pick-command')) {
if (!rowId || (!fieldName && !actionName)) {
return null;
}
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,16 @@ function createSharedSchemaEditorController({
semanticValidationTimers.set(rowId, timerId);
};

const scheduleFocusSettledSemanticValidationForRow = (rowId) => {
clearSemanticValidationTimer(rowId);
if (typeof timerApi?.setTimeout !== 'function') {
applySemanticValidationForRow(rowId);
return;
}
const timerId = timerApi.setTimeout(() => applySemanticValidationForRow(rowId), 0);
semanticValidationTimers.set(rowId, timerId);
};

const updateTextElementFromRows = () => {
const textElement = getTextElement();
if (textElement) {
Expand Down Expand Up @@ -794,7 +804,7 @@ function createSharedSchemaEditorController({
if (!rowId) {
return;
}
scheduleSemanticValidationForRow(rowId, { immediate: true });
scheduleFocusSettledSemanticValidationForRow(rowId);
};

const handleClick = async (event) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,6 @@ function renderSharedSchemaRows({
rowElem.setAttribute('data-row-id', row.id);
rowElem.setAttribute('data-row-index', String(index));
rowElem.innerHTML = `
<div class="${SHARED_SCHEMA_ROW_ACTIONS_CLASS}">
<button class="icon-button ${SHARED_SCHEMA_DRAG_HANDLE_CLASS}" type="button" data-action="drag" data-row-id="${row.id}" title="Drag field to reorder" draggable="true" aria-label="Drag field to reorder">${renderIconHtml('grip-vertical')}</button>
<button class="icon-button" type="button" data-action="add" data-row-id="${row.id}" title="Add field" aria-label="Insert field after this row">${renderIconHtml('plus')}</button>
<button class="icon-button" type="button" data-action="remove" data-row-id="${row.id}" title="Remove field" aria-label="Remove field">${renderIconHtml('minus')}</button>
<button class="icon-button" type="button" data-action="up" data-row-id="${row.id}" title="Move up" aria-label="Move up" ${index === 0 ? 'disabled' : ''}>${renderIconHtml('arrow-up')}</button>
<button class="icon-button" type="button" data-action="down" data-row-id="${row.id}" title="Move down" aria-label="Move down" ${index === schemaRows.length - 1 ? 'disabled' : ''}>${renderIconHtml('arrow-down')}</button>
</div>
<input type="text" data-field="name" class="${hasNameValidationError ? SHARED_SCHEMA_FIELD_INVALID_CLASS : ''}" placeholder="Column Name" aria-label="Column Name" value="${escapeHtml(row.name)}">
<select data-field="sourceType" class="${hasCommandValidationError ? SHARED_SCHEMA_FIELD_INVALID_CLASS : ''}" aria-label="Field type">
<option value="${SOURCE_TYPE_ENUM}" ${normalisedSourceType === SOURCE_TYPE_ENUM ? 'selected' : ''}>enum</option>
Expand Down Expand Up @@ -153,6 +146,13 @@ function renderSharedSchemaRows({
? `<div class="${SHARED_SCHEMA_ROW_VALIDATION_CLASS}" role="status">${escapeHtml(validationMessage)}</div>`
: ''
}
<div class="${SHARED_SCHEMA_ROW_ACTIONS_CLASS}">
<button class="icon-button ${SHARED_SCHEMA_DRAG_HANDLE_CLASS}" type="button" data-action="drag" data-row-id="${row.id}" title="Drag field to reorder" draggable="true" aria-label="Drag field to reorder">${renderIconHtml('grip-vertical')}</button>
<button class="icon-button" type="button" data-action="add" data-row-id="${row.id}" title="Add field" aria-label="Insert field after this row">${renderIconHtml('plus')}</button>
<button class="icon-button" type="button" data-action="remove" data-row-id="${row.id}" title="Remove field" aria-label="Remove field">${renderIconHtml('minus')}</button>
<button class="icon-button" type="button" data-action="up" data-row-id="${row.id}" title="Move up" aria-label="Move up" ${index === 0 ? 'disabled' : ''}>${renderIconHtml('arrow-up')}</button>
<button class="icon-button" type="button" data-action="down" data-row-id="${row.id}" title="Move down" aria-label="Move down" ${index === schemaRows.length - 1 ? 'disabled' : ''}>${renderIconHtml('arrow-down')}</button>
</div>
`;

const schemaHelpElement = rowElem.querySelector('[data-field="faker-doc-link"]');
Expand Down
Loading