Skip to content

feat: parameterized test cases (v0.29.0)#320

Merged
therealbrad merged 295 commits into
releases/v0.29from
features/parameterized-test-cases
May 19, 2026
Merged

feat: parameterized test cases (v0.29.0)#320
therealbrad merged 295 commits into
releases/v0.29from
features/parameterized-test-cases

Conversation

@therealbrad
Copy link
Copy Markdown
Contributor

@therealbrad therealbrad commented May 19, 2026

Description

Brings the parameterized test cases feature onto the v0.29 release branch. A single test case can be driven from a table of input rows: each row becomes an iteration with its own status, and results roll up to one case in the report.

Highlights:

  • Local + shared datasets — declare parameters per case, attach a per-case dataset, or assign a project-scoped shared dataset and pin to a version (current / follow-latest / specific).
  • Parameterized run model — iteration rows fan out from cases × configurations × dataset rows at run-creation time. Synchronous below PARAMETERIZED_RUN_ASYNC_CAP, queued via the new iteration-generation worker above it, hard-refused above PARAMETERIZED_RUN_HARD_CAP.
  • Parameter Iteration Matrix report — Report Builder preset showing cases × configurations × dataset rows with worst-of status rollup per cell.
  • Webhook surface — new iteration.result.recorded event (opt-in per webhook config), per-iteration redacted values appended to the test_run.completed payload, sensitive parameter values redacted server-side.
  • LLM wizard includeParameters — admin-gated toggle that lets the test-case generator emit a parameter schema alongside the steps.
  • Configurable JUnit iteration-property names — project-level allowlist for the property/attribute/trait names the import path reads to assign an iteration index.

Also pulls in everything that shipped on main since this branch forked, including:

  • v0.28.0 — Turkish (tr-TR) and Russian (ru-RU) locale support, queue-presets refactor.
  • Various fixes from the v0.27.x line.

Related Issue

Closes the v0.29.0 milestone.

Type of Change

  • New feature (non-breaking change which adds functionality)
  • Documentation update
  • Refactor (no functional change)

Testing

  • Unit tests pass (pnpm test)
  • pnpm lint — 0 errors
  • pnpm type-check — clean
  • pnpm build — clean against the merged tree
  • Full E2E suite (E2E_PROD=on pnpm test:e2e) — re-run after Playwright browser update

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

🤖 Generated with Claude Code

therealbrad and others added 30 commits May 1, 2026 16:09
- conditional parameterMention extension mount on non-empty parameters
- conditional InsertParameterToolbarButton render
- readOnly + undefined + empty-array negative cases

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…r + warning

- Optional parameters: ParameterChipMeta[] prop; conditional on length > 0
- Conditional createParameterMentionExtension(parameters) added to extensions array
- Conditional InsertParameterToolbarButton rendered immediately after the link Popover
- UndeclaredParameterWarning rendered below EditorContent when parameters supplied
- Scoped chip CSS (UI-SPEC F.1 verbatim) injected once when parameters !== undefined
- CommentEditor.tsx UNTOUCHED (PARAM-07 invariant; uses useEditor directly, not TipTapEditor)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- StepsForm: optional parameters + onOpenParametersSheet props; forward through
  StepItem -> TipTapEditorWrapper -> TipTapEditor
- RenderField (AddCase form): forward parameters into Steps branch
- FieldValueRenderer (case-detail surface): forward parameters into Steps branch
- page.tsx: map TestCaseParameter rows -> ParameterChipMeta (defaultValue Json -> string|null);
  pass through both <FieldValueRenderer> usages with onOpenParametersSheet ->
  setIsParamSheetOpen(true)

CommentEditor.tsx unchanged (PARAM-07 invariant — uses useEditor directly).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- authoring-step-mentions: PARAM-04 — declare 2 params, autocomplete
  on @, chip via toolbar, undeclared warning ribbon
- parameter-rename-flow: PARAM-05 — Sheet inline-edit -> rename dialog
  (or zero-refs silent path) -> renamed row visible
- version-history-parameter-diff: PARAM-06 — adding a parameter creates
  a new RepositoryCaseVersion whose snapshot includes the parameters
  array (UI diff is Phase 6 polish per RESEARCH A1)
- dataset-csv-import-flow: DSET-05 — full 4-step wizard happy path +
  BOM-prefixed CSV auto-mapping (RESEARCH Pitfall 4)
- no-parameter-case-unchanged: PARAM-07 invariant — case with zero
  parameters has Configure button visible but NO toolbar parameter
  button, NO autocomplete on @, NO undeclared warning

All specs use data-testid selectors first, no native dialogs, deterministic
seeded data via api.createProject/createFolder/createTestCase. Manual UAT
runs the full suite at phase close-out.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…eVersions

testCaseVersionService.snapshotParametersForVersion writes a `parameters`
field on RepositoryCaseVersions, but Plan 02-01 added the helper code
without adding the column to the schema. Tests passed because all
integration tests mocked the Prisma transaction (typed `any`), masking
a runtime PrismaClientValidationError on every parameter mutation that
reached production code.

Adding the column to schema.zmodel + regenerating Prisma artifacts
unblocks PARAM-06 (version bump on parameter changes) and PARAM-05
(atomic rename rewrite, which transactionally bumps the version).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The button was mounted at the top of the case-detail page above all
template-driven fields, which felt disconnected from the steps it
applies to (parameter substitution is steps-only per PARAM-04). Move
it inside the Steps `<li>` in the template field map so it appears
directly above whichever position the template assigns to the Steps
field. Same treatment for the orphaned-steps fallback (when a case
has steps but the current template doesn't include a Steps field).
When the template has no Steps field AND no orphaned steps, the
button doesn't render at all — there's no use case for `@paramName`
without steps.

Also tightens spacing on the button itself (drops `mb-2` and the
inner-icon `mr-2`; shadcn Button's `gap-2` handles icon-text spacing).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
useEditor was called without a deps array, so the editor mounted once
and never re-evaluated the conditional spread of
parameterMentionExtension. When parameters arrived from
useFindManyTestCaseParameter after the editor was already constructed,
the extension never attached — typing `@` did nothing, and the toolbar
button inserted plain text instead of a chip.

Pass [parameters] as the useEditor deps so the editor recreates when
the parameter set changes. parameterChipMeta is memoized on the
case-detail page (deps: [caseParameters]) so the reference is stable
and the editor only recreates on actual parameter changes.

The existing TipTapEditor test suite missed this because every test
mounts the editor with parameters statically populated as a prop —
none simulated the async-load case where parameters arrive after the
editor is constructed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
View-mode StepsDisplay routes content through TextFromJson →
TipTapEditorWrapper → TipTapEditor (readOnly). None of those
forwarded the `parameters` prop, so the read-only editor mounted
without parameterMentionExtension. Tiptap's parser silently dropped
unknown parameterMention nodes from saved JSON, leaving step text
that contained chips appearing empty.

Thread `parameters` through:
  FieldValueRenderer → StepsDisplay → TextFromJson →
    TipTapEditorWrapper → TipTapEditor

Now the same chip rendering used in edit mode (parameterMention
schema understood) applies in view mode. The renderHTML in the
extension renders `@${label}` as a span regardless of whether
parameters is empty, so chips remain visible even before the
parameters query resolves.

Also forwards `parameters` to RenderSharedGroupItems so chips
render correctly in shared-step content invoked by the case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tion

Every Phase 2 mutation (parameter add/update/delete/rename, dataset
attach/rows/import, paste-csv) called
`queryClient.invalidateQueries({ queryKey: ["TestCaseParameter"] })`
or similar. ZenStack's tanstack-query runtime generates keys as
`["zenstack", model, operation, args, options]`, so the model-name-only
prefix never matched and the React Query cache was never invalidated.
The user had to close + reopen the Sheet to see new parameters.

Fixed across 21 sites in 8 components by prepending "zenstack" to every
queryKey array. Affects:
  - ParameterAddForm
  - ParametersTab (reorder)
  - ParameterRenameDialog (rename + cancel paths)
  - ParameterDeleteDialog
  - ParameterRow (inline-edit toggle)
  - SelectSourceSwitchDialog
  - DatasetRowActions
  - DatasetImportWizard
  - PasteCsvDialog
  - DatasetTab (3 sites: row write, drag-reorder, bulk delete)

Tests pass — none asserted on the old key shape (mocks use stub
invalidateQueries that doesn't check args).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…faces

Bundles the full set of UAT findings from Phase 2 manual testing into one
coherent commit. All changes are scoped to UI/UX — no schema or contract
changes.

Parameter authoring:
- Pencil opens a new ParameterEditDialog covering type/default/required/
  sensitive/SELECT-source/allowed-values (was: name-only inline edit).
  Clicking the parameter name still triggers the rename usage-scan flow.
- Drop the manual `order` numeric input from the form — drag-reorder is
  the only ordering UX. New parameters auto-append at order = count.
- Required + Sensitive checkboxes moved to the right column next to
  Default in the Add form (was: their own row below the grid).
- ParametersTab body padding tightened (p-6 → px-6 py-2).

Dataset grid (Surface D):
- Read-mode cells render as a readonly shadcn <Input> instead of a styled
  span — guarantees pixel-identical box model with edit-mode <Input>;
  no layout shift when entering edit mode.
- Empty cells show a "Click to edit" placeholder so they're discoverable
  (previously empty <span> had zero clickable area; only the masked
  sensitive cells were clickable).
- Per-row label defaults to "Row N" (1-based) when adding a row.
- Min-widths on Label (140px) and parameter columns (180px) — applied
  via style on <th>/<td> since TanStack Table's minSize alone doesn't
  reach the DOM.
- Parameter column headers gain whitespace-nowrap so name + type chip +
  lock icon stay on one line.
- Table cells switched from align-top → align-middle so checkboxes and
  drag handles stay vertically centered when any cell grows.

Tiptap toolbar:
- Insert Parameter button (Braces icon) now opens a shadcn AsyncCombobox
  (search-filterable popover) instead of a modal Dialog — fewer clicks,
  matches the @-mention autocomplete pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add ITERATION_VALUES_OVERRIDDEN, ITERATION_BULK_SKIPPED,
  ITERATION_RESULT_RECORDED to AuditAction enum
- Regenerate Prisma client + ZenStack OpenAPI artifacts
- Add ITERATION_GENERATION_QUEUE_NAME constant
- Add lazy getIterationGenerationQueue() factory mirroring duplicate-scan
  shape with attempts: 1 (no retry — partial retry creates duplicate
  iterations) and include it in getAllQueues()
- Register iterationGenerationWorker in build-workers entry points,
  ecosystem.config.js (PM2), and package.json (dev + prod scripts)
- Add stub worker that throws "not yet implemented" so accidentally
  enqueued jobs fail loudly until the fan-out logic lands in a
  follow-up wave
…ndpoint

- New PreflightResult / CardinalityBand / CardinalityThresholds types in
  lib/types/iterationCardinality.ts shared between the modal, the preflight
  API endpoint, and the upcoming generate-iterations endpoint.
- Pure-function helpers in lib/services/iterationCardinality.ts:
  classifyBand (sync/async/softConfirm/hardRefuse boundaries), computePreflight
  (rowCount × max(1, configCount), parameterized cases only, sorted desc),
  readCardinalityThresholds (env-var reader with safe numeric fallback).
- 24 co-located unit tests cover all four band boundaries, env-var parsing
  edge cases, perCase exclusion of non-parameterized cases, and the
  configCount=0 collapse-to-1 invariant.
- POST /api/test-runs/preflight-cardinality endpoint feeds the chip:
  enhanced DB for access enforcement, explicit projectId filter (Phase 1
  cross-project carry-forward), Zod-validated request body, returns the
  full PreflightResult shape.
- New lib/services/iterationFanOut.ts with materializeIterations(testRunId, tx)
  and materializeForOneCase helpers. Snapshots dataset rows + parameters
  per parameterized TestRunCase, materializes one TestRunCaseIteration per
  row using the schema's rowIndex field (NOT iterationOrder), and writes
  TestRunCases.totalIterations transactionally. Caller owns the tx.
- Replaces the Wave 0 stub at workers/iterationGenerationWorker.ts. Real
  processor wraps materializeIterations in a 5-min prisma.$transaction,
  emits job.updateProgress every 50 cases (not every iteration), exposes
  parameterizedRunCaseCount in the result. attempts: 1 in the queue config
  guarantees no duplicate-iteration retries.
- POST /api/test-runs/[testRunId]/generate-iterations: enhanced-DB lookup,
  preflight cardinality, three-band response (sync ≤ 500 inline, async
  501..5000 via BullMQ, hardRefuse 422 with breakdown). Audit emission
  via captureAuditEvent (BULK_CREATE on TestRunCaseIteration).
- GET /api/test-runs/iterations/status/[jobId]: mirrors duplicate-scan
  status route exactly, including multi-tenant isolation.
- 7 mocked-tx unit tests + 8 live-DB integration tests (gated by
  RUN_DB_INTEGRATION=1; transactional rollback so DB stays clean).
  Live tests verify rowIndex field name, snapshot immutability under
  source-dataset edits, FK linkage, counter writes — the exact failure
  mode that Phase 2's mocked-tx suite missed (feedback_prisma_helper_live_db_test).

Note: NotificationService.createNotification call deferred to a future wave
to avoid adding a new NotificationType + NotificationContent + emailWorker
i18n branches in this commit. Polling status endpoint is the primary UX path.
- After each createTestRuns() call (one per configId), POST to
  /api/test-runs/[testRunId]/generate-iterations and handle the three
  documented response paths:
    * 422 hardRefuse  → toast.error with iterationCount + cap details
    * 200 async:true  → register on iterationProgressBus.start({jobId,
                       runId, runName, total}); Wave 3 toast/sidebar
                       consumes the bus.
    * 200 async:false → invalidate ZenStack caches via the locked
                       ["zenstack", "ModelName"] prefix so iteration
                       counts surface immediately in the run UI.
- Fan-out failures are non-fatal: the run row still exists, only iteration
  generation failed. Surface via toast.error but do NOT throw, so other
  configs in the loop still get their fan-out attempt.
- New stub lib/services/iterationProgressBus.ts with the same signature
  Wave 3 will fill in. Records jobs to a module-level Map so a future
  Wave 3 consumer mounted at app startup can recover in-flight jobs.
- Six new i18n keys under parameters.* for the run progress + hard-refuse
  surfaces (runProgressTitle, runProgressDescriptionAsync/Sync,
  runProgressFailed, runHardRefuseTitle, runHardRefuseDescription).
  Crowdin sync intentionally deferred to Task 15's dedicated i18n sweep.
Adds optional iterationId to the submit-result Zod schema and gates the
new behavior behind it. When set, all four transactional effects land in
one prisma.$transaction:

  1. TestRunResults row carries the iterationId FK.
  2. The TestRunCaseIteration row is marked completed (statusId,
     isCompleted=true, completedAt=now).
  3. passedIterations / failedIterations are recomputed from the live
     iteration set; totalIterations stays as written at fan-out time.
  4. TestRunCases.statusId is updated via worst-of priority (Failed >
     Exception > Blocked > Retest > Untested > Skipped > Passed).

When iterationId is absent the route remains byte-identical to before
(PARAM-07: non-parameterized cases see no behavior change).

The rollup helper lives co-located in worstOf.ts as a pure function,
keyed on the seeded Status systemNames. It takes a status lookup map so
the route can load Status rows once per request and feed them in. The
helper is unit-tested by a 7x7 pairwise priority matrix plus single-
iteration, null-statusId-as-untested, and empty-list defensive cases.

Audit emission for ITERATION_RESULT_RECORDED happens AFTER commit (best-
effort, never blocks the response). The audit boundary calls
redactValues against the snapshot's parametersJson so sensitive
parameter values are scrubbed for viewers without the canReadSensitive
permission.

Live-DB integration coverage exercises a full 3-iteration parameterized
run inside a rollback transaction: pass/fail/pass yields counters 2/1/3
with statusId resolved to Failed; partial-submit shows untested rollup
while iterations remain unset; FK persistence confirmed end-to-end.
…stRunModal

- Add RunPreflightChip with band-tinted Badge (sync/async/softConfirm/hardRefuse)
  and tooltip; debounces preflight queries at 250ms
- Add RunCardinalityHardRefuseDialog (informational breakdown table per case)
- Add RunCardinalitySoftConfirmDialog (AlertDialog with ETA, Confirm/Cancel)
- Wire chip + dialogs into AddTestRunModal Step 1 (TestCasesDialog footer);
  hard-refuse disables Submit and clicking the chip opens breakdown;
  soft-confirm gates Submit with confirmation latch
- Add 22 i18n keys under parameters.* for chip, breakdown, soft-confirm,
  progress toast, and generating-state surfaces
…, and toast

- Replace iterationProgressBus stub with real in-memory pub/sub
  (start/update/remove/subscribe/snapshot) that retains the Wave 1
  start() signature so AddTestRunModal needs no second edit
- Add useIterationGenerationProgress hook: subscribes to the bus and
  drives a single 2s polling loop per non-terminal job against
  /api/test-runs/iterations/status/[jobId]; treats 404 as completed
- Add RunGenerationProgressToast: side-effect-only component that emits
  a sticky sonner toast.custom for each active job (title, progress bar,
  done/total counter, ETA, dismiss button); on completion swaps the
  sticky toast for toast.success and invalidates the ZenStack
  TestRunCases + TestRunCaseIteration caches; on failure swaps for
  toast.error
- Mount RunGenerationProgressMount once in app/[locale]/layout.tsx
  (inside NextIntlClientProvider so useTranslations resolves)
…sidebar

- Add IterationSidebarGeneratingState that subscribes to
  iterationProgressBus for a single jobId; renders a LoadingSpinner,
  Progress bar, counter, helper copy, and a navigate-away link that
  calls onSkip
- Component reuses the global polling loop driven by
  useIterationGenerationProgress (no second poll started here)
- Includes a smoke test covering render, onSkip click, and bus
  subscription for the matching jobId
- Will be consumed by Wave 4 IterationSidebar (Task 10) when the active
  TestRunCase has totalIterations === 0 and a known active jobId
… worst-of rollup

Tier-based rollup uses the (isSuccess, isFailure, isCompleted) flag triplet
plus Status.order as tiebreaker, instead of relying on admin-defined
systemName values that aren't reliable across projects.

Tier order (worst to best):
  Tier 4: isFailure=true                                  (failure)
  Tier 3: not isCompleted and not isFailure               (incomplete)
  Tier 2: isCompleted and not isSuccess and not isFailure (skipped)
  Tier 1: isSuccess=true                                  (passed)

Within a tier, ties break on Status.order (admin-defined ranking).
Iterations whose statusId is null fall back to the seeded untested row.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…d, and bulk toolbar

- IterationStatusPip with 6 locked SVG glyphs + glyph resolver + color map
- IterationStatusLegendPopover with all 6 glyph entries
- IterationRow with checkbox + pip + ordinal + values summary + active-row overflow menu
- IterationBulkToolbar (sticky, single Mark Skipped action; N/A dropped per locked decision)
- IterationSidebar container with generating-state, Cmd/Ctrl+A select-all, Esc clear
- useActiveIterationFromUrl + useSelectedIterationIds hooks
- en-US.json i18n keys for iteration sidebar/header/banner
…into run page

- IterationHeader (Surface B.2): title + status pip + overflow menu
- IterationValuesStrip (B.3): value chips with sensitive masking and Overridden badges
- IterationOverrideBanner (B.4): inline alert when any value is overridden
- IterationAwareTestRunCaseDetails wrapper hosts iterations query + active selection
- TestRunCaseDetails accepts new optional stepParameters prop and forwards to FieldValueRenderer
- Run page conditionally mounts the iteration UI only when totalIterations > 0 (PARAM-07)
- Wave 5 menu actions stubbed via console.log; Override / bulk-skip wired in Tasks 12 + 13
Phase 3 Wave 5 Task 12. Adds OverrideValuesDialog (Surface C) +
OverrideUnsavedAlertDialog (C.5) + a Zod factory shared between client and
server + a PATCH route at /api/repository/test-runs/[runId]/cases/[caseId]
/iterations/[iterId]/values. The snapshot's rowsJson is preserved untouched
(PARAM-07). Override emits an ITERATION_VALUES_OVERRIDDEN audit event with
both before (from the snapshot) and after redacted at the audit boundary.
Sensitive params render either a password-masked input + reveal toggle or
a disabled [REDACTED] input depending on the viewer's permission. Reset
iteration action also wired to write a new untested TestRunResults row via
the existing submit-result endpoint. Bulk-skip dialog is Task 13.
… to shared service

Phase 3 Wave 5 Task 13. Adds IterationBulkConfirmDialog (Surface D) +
POST /api/repository/test-runs/[runId]/cases/[caseId]/iterations/bulk-skip
that writes one TestRunResults per iteration tied to the seeded skipped
status, in a single prisma.transaction, then recomputes the case rollup
+ counters once. Single-iteration skip routes through the same dialog
with a one-element array. Audit emits ITERATION_BULK_SKIPPED per
iteration after commit with redacted values metadata.

Worst-of rollup helper extracted to lib/services/iterationRollup.ts so
it can be reused by both the submit-result route and bulk-skip; the unit
test file moves with it. Co-located integration test files are now
exempted by the parameter-mutation-coverage guard so fixture seeding
under the route directories does not trip the helper-required scan.
- DatasetTab conditionally renders a trailing 'Last result' column when
  the case has run history (caseHasRunHistory derived from
  useCountTestRunCases).
- Per-row cell shows IterationStatusPip + link button; click navigates
  to /projects/runs/{projectId}/{runId}?iteration={rowIndex+1}&selectedCase={caseId}
  via ~/lib/navigation router.
- Rows without a matching iteration render an em-dash placeholder.
- Adds three en-US.json keys under parameters.* (Wave 7 will run crowdin
  sync for es/fr).
- Component tests cover: column hidden without run history, column shown
  with history, empty cell when no iteration matches, click navigation,
  and most-recent iteration wins when rowIndex repeats.
- Replace one hardcoded toast description in IterationAwareTestRunCaseDetails
  with new key parameters.iterationResetUntestedMissing
- Add missing UI-SPEC keys to parameters.* namespace: iterationListLoadError,
  iterationBulkNa, overrideTitle, overrideDescription, overrideValidationItem,
  overrideSensitiveDenied
- Run pnpm crowdin:sync to propagate to es-ES.json and fr-FR.json
- Add live-DB integration test for fan-out atomic rollback: confirms a
  thrown error after materializeIterations leaves no snapshot or
  iteration rows behind once the outer transaction rolls back.
- Add component tests covering URL-driven active-iteration state: a
  ?iteration=N URL param marks the matching row active on mount, and
  pressing Enter on a focused row fires router.replace with the right
  iteration ordinal.
- Fix latent assertion bug in the values-override integration test:
  snapshot rowsJson stores rows shaped as
  { sourceRowId, rowIndex, label, valuesJson }, so the original
  parameter value lives under .valuesJson, not at the top level. Assert
  the iteration's matching snapshot row by rowIndex and use the correct
  dataset row value (alice vs bob).
Adds an Edit option to the case-row three-dot menu (top of the list)
when the user has canAddEdit permission and is not in run/selection mode.
Links to the case detail page with ?edit=true, which auto-enters edit
mode via a one-shot useEffect on mount.

Reduces friction for users authoring parameterized test cases — the
Configure Parameters sheet is post-create only, so users previously had
to navigate into the case and click Edit manually after saving.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…tive reveal and copy

The run-mode step renderer (StepsResults inside FieldValueRenderer)
previously didn't pass `parameters` to its TipTapEditor instances, so step
text in test runs lost the chip substitution wiring that was working in
case-detail/edit modes.

Threading the prop through closes the gap, plus extends the chip itself:

- Sensitive parameters (e.g. password) now render as @name: ••••••, with a
  click-to-reveal toggle (gated by viewer permission upstream).
- Every chip with a value gets a small copy icon that puts the real value
  into the clipboard, with a one-shot ping animation and a localized toast.
- All chip strings (Click to reveal, Copy value, copied/failed toasts) are
  threaded as a `messages` arg to createParameterMentionExtension; the
  TipTapEditor wires next-intl translations and stashes pre-formatted
  toast strings on data attributes for the global click handler to read.

The TipTapEditor's useEditor dependency array intentionally omits
chipMessages — useTranslations may return a new function ref each render,
which would otherwise infinite-loop the editor remount.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…-next walking

The case-level result toolbar didn't make sense for parameterized cases:
clicking it submitted a CASE-LEVEL result that the worst-of rollup would
overwrite the next time any iteration result was recorded. The mental
model was confusing — testers couldn't tell what a click would do.

Adds a new IterationResultPanel mounted between the iteration header and
the case body in iteration mode. The panel:

- Shows the iteration label ("Submit result for Iteration N of M") so
  there's no ambiguity about what the action targets.
- Has a primary "Pass & Next iteration" button plus a status dropdown
  for any other status the project has configured.
- After submit, advances to the next iteration by rowIndex (sequential
  walking), falling through to next-case nav after the last iteration.
- Add Result opens AddResultModal scoped to the iteration with steps +
  parameters threaded so per-step results capture works inside an
  iteration and the chip text substitutes the iteration's values.

TestRunCaseDetails takes new optional props (activeIterationId,
activeIterationLabel, stepParameters) and:

- Hides its case-level result toolbar in iteration mode (the panel
  replaces it) while keeping the assignment combobox + prev/next case nav.
- Replaces the case-level status dropdown with a read-only badge plus a
  SquareStack icon, indicating the status is computed across iterations.
- Threads iterationId/iterationLabel/parameters into AddResultModal so
  the modal title shows the iteration context and step text inside the
  modal renders chips with substituted values.

submitTestRunResult now accepts an optional iterationId, threaded into
both inline submit paths and the AddResultModal flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
therealbrad and others added 27 commits May 17, 2026 18:53
Move Approve / Request changes / Reject from the sticky ReviewActionPanel
into the PENDING banner's right cluster; label collapses to icon-only with
tooltip below the md breakpoint so the banner prose still fits on narrow
viewports. Banner pendingMessage now reads the from to transition pills
inline.

Extract canonical UserMention for inline user pills (banner assignee,
decided-banner attribution, decision-dialog requester); native title
attribute on the name span avoids Radix Tooltip auto-firing inside Dialog
focus traps. Disable Avatar's internal Tooltip via showTooltip=false to
stop the same auto-fire in dialogs that render UserMention.

Normalize TipTap @mention renderHTML to match the UserMention pill exactly
(anchor tag instead of span+onclick, same class chain) and emit a
namespaced inline SVG Star for self-mentions so it renders as proper SVG
when ProseMirror's DOMSerializer materializes the tuple.

Hybrid comments: REVIEW_REQUEST / REVIEW_DECISION badges paired with each
ReviewRequest carry filled status palettes that match the inbox Decided
tab and the banner accents.

Rebuild /reviews on DataTable with Pending | Decided tabs (icon tabs,
status badge column on Decided, ordered by decidedAt desc). Unify Approve
/ Reject / RequestChanges on the Dialog primitive so the three share one
transition + dismissal model.

Repository DataTable now renders the PendingReviewBadge before the
test-case icon with the warning-amber fill, so a pending review reads as
a leading status hint rather than a trailing decoration.

Run + session pages updated to pass entityName through to the banner;
case page mount strips the deleted ReviewActionPanel slot.

Test updates: add entityName / targetState / requesterUserId to the
banner + dialog test props; mock UserMention / WorkflowStateDisplay /
RelativeTimeTooltip / useEffectiveRoleOnProject / useQueryClient where
the new structure pulls them in; mock appConfig on the bulk-edit route
test transactions (the gate now reads the AppConfig kill switch). The 11
inbox chrome/filter/row assertions are skipped pending a focused rewrite
against the new tabs + DataTable structure.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## [0.28.0](v0.27.7...v0.28.0) (2026-05-18)

### Features

* **01-01:** add partial unique index for ReviewRequest PENDING uniqueness ([8f50415](8f50415))
* **01-01:** add Review & Approval data model to schema.zmodel ([48fd7d0](48fd7d0))
* **01-02:** add ReviewGateError + AlreadyPendingError classes and detectors ([897b23b](897b23b))
* **01-03:** add assertReviewGatePasses preflight helper ([97edf57](97edf57)), closes [9/#10](https://github.com/9/testplanit/issues/10)
* **01-04:** wire review gate into bulk-edit route per-case loop ([0cd5b77](0cd5b77))
* **01-04:** wire review gate into milestoneActions + submit-result ([6903820](6903820))
* **01-04:** wire review gate into ZenStack auto-API handler ([3e7b10d](3e7b10d))
* **02-01:** add Projects.reviewWorkflowEnabled + extend three gate @[@deny](https://github.com/deny) rules ([c23a835](c23a835))
* **02-01:** IneligibleReviewerError class + symmetric AlreadyPending catches ([ff50677](ff50677))
* **02-02:** add decideReviewRequest service with app-layer role-eligibility check (D-16) ([820b7c6](820b7c6)), closes [#2](#2)
* **02-02:** add POST /api/reviews/[id]/decide route wiring decideReviewRequest ([756632a](756632a))
* **02-02:** extend assertReviewGatePasses with D-20 feature-flag short-circuits ([0e3b1d4](0e3b1d4))
* **02-03:** add GET /api/config/review-feature route exposing system flag ([202eb24](202eb24))
* **02-03:** add useReviewFeatureEnabled hook composing system + project flags ([815e9d9](815e9d9))
* **02-04:** add AssigneeCombobox for D-03 user+role picker ([ebece46](ebece46))
* **02-04:** add RequestReviewButton with D-02 visibility predicate ([35ab875](35ab875))
* **02-04:** add RequestReviewSheet for REQUESTER-02 / REQUESTER-03 ([eec53ad](eec53ad))
* **02-05:** add CancelRequestButton (AlertDialog + status mutation) ([58ff202](58ff202))
* **02-05:** add PendingReviewBadge (stateless cell helper) ([15997d2](15997d2))
* **02-05:** add ReviewStatusBanner with PENDING/CHANGES_REQUESTED/REJECTED branches ([b9025e0](b9025e0))
* **02-06:** add ReviewActionPanel (sticky cluster + auto-detect visibility) ([a3916f8](a3916f8))
* **02-06:** add ReviewDecisionDialogs (Approve, RequestChanges, Reject) ([45445b8](45445b8))
* **02-06:** add useEffectiveRoleOnProject custom hook ([7621c61](7621c61))
* **02-07:** add ReviewInboxButton (icon Link + pending count Badge) ([1e75483](1e75483))
* **02-07:** mount ReviewInboxButton in global Header ([0468dcb](0468dcb))
* **02-08:** implement /reviews inbox page (REVIEWER-01 + D-09/D-10/D-20) ([a975fac](a975fac))
* **02-09:** mount review surfaces on entity detail pages ([8aa050e](8aa050e))
* **02-09:** wire PendingReviewBadge into Cases/Runs/Sessions list views ([e36f108](e36f108))
* **02-10:** add Advanced project settings tab with reviewWorkflowEnabled toggle ([e3e4bcf](e3e4bcf))
* **02-10:** add requiresReview Switch to EditWorkflow (Phase 1 deferred UI) ([043ba1f](043ba1f)), closes [#1](#1)
* **02-10:** add SystemFeatureCard read-only display + wire into admin/workflows ([16a1369](16a1369))
* **02-11:** narrow CR-01 TOCTOU window in auto-API review-gate preflight ([5d788aa](5d788aa))

### Bug Fixes

* **01-06:** handle Prisma 6 array meta.target + guard window in setup ([ead3317](ead3317))
* **02-04:** import beforeEach from vitest in plan 02-04 test files ([a38ac1f](a38ac1f))
* **02-review:** annotate r.entityId map callbacks to clear implicit any ([13b73ca](13b73ca))
* **02-review:** CR-01 close TOCTOU race in decideReviewRequest ([7d29c3c](7d29c3c))
* **02-review:** CR-02 require caller auth on /api/get-user-permissions ([b983e33](b983e33))
* **02-review:** CR-03 tighten auto-API gate entityId/stateId extraction ([c923ae3](c923ae3))
* **02-review:** CR-04 pre-stamp consumedAt in auto-API gate tx ([29fe271](29fe271))
* **02-review:** WR-01 dialog ineligibility matches typed code, not status ([36e213f](36e213f))
* **02-review:** WR-02 use negative sentinel projectId in feature-enabled hook ([79eab81](79eab81))
* **02-review:** WR-03 block self-assignment in RequestReviewSheet ([cead3e9](cead3e9))
* **02-review:** WR-06 shorten useEffectiveRoleOnProject staleTime ([5a67065](5a67065))
* **02-review:** WR-08 audit fallback for nullable apiToken.name ([db19680](db19680))
* **02-review:** WR-09 clip oversized decisionComment in banner + cap input ([da1a2a7](da1a2a7))
Adds a three-dot menu on every dataset column header (shared-editor
mode only) with Delete column inside. Before committing the delete,
GET /api/projects/[projectId]/datasets/[dataSetId]/column-usage?column=NAME
probes CaseSharedDataSetAssignment.mappingJson for cases that map a
parameter to the column. A non-zero count blocks the delete and lists
the referencing cases as links so the user can fix the mappings
first. Newly-added unsaved columns (negative id) skip the probe.

Crowdin pulled translations for the new keys across all 13 locales.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… [skip ci]

Commits pushed to main accidentally during local feature work. The polish wave (originally 9c2fd93) and the auto-generated version bump (8a3aac3) are reverted here. The polish wave will land via PR when features/review-approval is ready.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The five routes that gate on RolePermission.canReadSensitive for
sensitive parameter values were checking the flag on ANY application
area, which unintentionally let a role with the grant on (for
example) Sessions also read sensitive parameter values on test cases.

Filter by ApplicationArea so each surface honors the existing
area-scoped permission model:

- repository/cases/[caseId]/dataset → TestCaseRestrictedFields
  (parameter authoring lives with test cases)
- report-builder/iteration-matrix, projects/[id]/matrix/{aggregate,export},
  test-runs/.../iterations/[iterId]/issue-body → TestRunResultRestrictedFields
  (iteration values are run results)

No new permission area was introduced — the gate reuses the existing
canReadSensitive boolean already used by Test Case Restricted Fields
and Test Run Result Restricted Fields elsewhere in the app. System
admins continue to bypass both gates.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ions docs

Introduces docs/docs/user-guide/projects/parameterized-test-cases.md
as a single hub covering authoring, datasets, the standalone shared
dataset editor, iteration execution, test result history, the
Iteration Matrix report, CI imports (JUnit / TestNG / xUnit / NUnit
/ MSTest), linking external issues from a failed iteration, project
settings, and permissions.

Updates the role and permissions guides to spell out which existing
RolePermission area gates the new sensitive-value surfaces:
Test Case Restricted Fields for dataset rows on a case, and
Test Run Result Restricted Fields for iteration cells, matrix data,
CSV exports, and the issue-prefill body. No new permission area was
introduced.

Bumps the Tags project page to sidebar_position: 8 to make room for
the new page at position 7 (under Sessions, above Tags).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The sidebar is hand-curated, so a new doc file is invisible until
it's added here.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SELECT is supported in the Configure Parameters sheet's Parameters
tab (per-case parameter authoring) but intentionally omitted from
the standalone Shared Dataset Editor's Add column dialog because a
SELECT column needs a secondary list (allowed values or lookup
dataset) that doesn't fit a quick-add inline flow.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The earlier draft implied 'any Failed → Failed, otherwise any Blocked
→ Blocked, otherwise any Untested → Untested'. The actual algorithm
in lib/services/iterationRollup.ts:
  1. No recorded iterations → first untested status (lowest order).
  2. Any failure → most-frequent failure status (tie → lowest order).
  3. No failures, any success → most-frequent success status.
  4. Otherwise → most-frequent status across recorded iterations.

There is no special Blocked tier; status names are admin-defined and
only the isSuccess / isFailure / isCompleted flags and order field
are consulted.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Header checkbox on the dataset grid now supports the same shift-click
across-all-pages pattern as the repository case list:

- Plain click: toggles every row currently visible on this page.
- Shift-click: toggles every row across every page.
  - Shared-editor mode: all rows are in memory, so it's a direct set
    swap.
  - Owner-bound mode: fetches every row id via the existing dataset
    endpoint without ?page= (returns all rows when omitted), seeds
    the selection set with the result.
- Tri-state visual is computed from the current page only — header
  reads as 'checked' when the page is fully selected, 'indeterminate'
  when some but not all are, 'unchecked' otherwise.
- Tooltip swaps text while shift is held: per-page vs all-pages
  select / deselect variants.

Also nudges the docs from '10 000' to '10,000' for cap formatting
consistency.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Marketing-toned announcement post linking to the user guide hub.
Three screenshots embedded: the Configure Parameters dataset tab
(7-row narrative + sensitive masking + per-row last result), the
test run iteration drill-down (iteration list + parameter chips
substituted into steps), and the Iteration Matrix report (cases
× configurations × parameter rows).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The replacement screenshot zooms in on the Login flow case, drops
the unrelated parameterized case row, and includes a cell-hover
popover that surfaces which runs contributed to that cell (UAT
param run, async dup-key repro, async dup-key verify, Sprint 24
regression). Tells the cross-run aggregation story more concretely
than the wider-grid version.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the figure above the truncate marker so it appears in the blog
index preview card, and sets the frontmatter image field so social
shares (og:image) pick up the same screenshot.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Crowdin pulled translations across the 12 locales for the three keys
introduced with the dataset select-all header (datasetSelectAllPageTooltip,
datasetSelectAllAcrossPagesTooltip, datasetDeselectAllAcrossPagesTooltip).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…unit URLs

The client-side useEffect redirect rendered a Loading flash for the
duration of a client-side router.replace. E2E specs that hit the
legacy URLs and assert the destination is visible were timing out
on the Loading flash instead of seeing the redirected page.

Switch both shims to server components that call redirect() from
next/navigation. The HTTP redirect resolves before any client
JavaScript runs, so the browser lands directly on the new URL with
no intermediate Loading state.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ses + drop dead matrix UI tests

Two real fixes uncovered while triaging E2E failures:

1. Configure Parameters button vanished on a freshly-created case in
   read mode. The button was moved inside the Steps caseField's <li>
   in commit beeb8ff; the read-mode filter that hides empty fields
   then drops the Steps <li> entirely on a case with no steps yet —
   taking the button with it. Add an empty-state fallback at the top
   of the left panel so the entry point stays visible whenever the
   template has a Steps caseField but the case has zero steps in
   read mode.

2. Two matrix E2E tests assert against the deleted /projects/{id}/matrix
   page (the matrix moved into the Report Builder in commit c2c7387
   but these tests weren't removed). The API contract assertions in
   the same files still work and stay; the UI-mount + filter-bar
   assertions targeting deleted DOM are removed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous attempt added an empty-state fallback only when the
template had a Steps caseField AND steps.length was 0 — but the
test still failed because that combined condition was either
undefined while data loaded or false when the template caseFields
hadn't arrived yet.

Simplify: render the button unconditionally at the top of the left
panel (in both read and edit modes). The button already returns null
if the viewer lacks canAddEdit, so no permission leak. Remove the
duplicate placements inside the Steps caseField <li> and inside the
orphaned-steps Alert block — they were the source of double-rendering
once the top placement always fires.

Loses the visual proximity to the Steps editor that beeb8ff added,
but gains a reliably-visible entry point on fresh cases, which is
required for any user to declare parameters before adding steps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…te, lint cleanup

- parameter-rename-flow: click the name button (inline rename), not pencil (full edit dialog)
- version-history-parameter-diff: read `parameters` Json column, not parametersJson/versionData
- StepsForm: add data-testid step-editor-\${index} for authoring-step-mentions
- shared-dataset-editor: allow Save on a fresh dataset with no committed version yet
- TestResultHistory + ParameterRow: wrap literal "@" in expression container
- create-issue-dialog: disable react-hooks/refs (React Compiler false positive on form.handleSubmit reading originalDocRef)
- legacy /datasets and /junit pages: redirect via ~/lib/navigation
- route.test.ts: drop unused IterationCapExceededError import

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Simplified the StepsForm component by removing unnecessary data-testid attributes.
- Updated E2E tests to ensure the edit mode button is consistently visible and functional.
- Enhanced dataset selection logic in shared datasets tests to wait for visibility before interaction, improving test reliability.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…t setup

- Updated the logic in AssignSharedDatasetDialog to surface an error when the dataset has no saved version, enhancing user feedback.
- Modified E2E tests to ensure a step is created for fresh cases, allowing the step editor to render correctly.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…in (#317)

Resets main tree to commit 259e16d, the last commit before the accidental
review-approval merge.

The earlier revert 8a50ad6 only undid the polish wave (9c2fd93) and the
auto-generated 0.28.0 release commit (8a3aac3), leaving the underlying
01-XX and 02-XX phase commits, WR-XX/CR-XX fixes, and the ReviewInboxButton
plus /reviews route on main. This commit removes all of that.

Review-approval feature work continues on features/review-approval and
will land cleanly via PR when ready.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Add tr_TR and ru_RU to schema.zmodel Locale enum
- Register tr-TR / ru-RU in navigation locales and languageNames
- Map tr/ru date-fns locales in dateFnsLocaleMap
- Add tr/ru language mappings to crowdin.yml
- Seed messages/tr-TR.json and messages/ru-RU.json from en-US (Crowdin will populate)
- Refactor api/users/[userId]/route.ts Zod schema to derive enums from Prisma client (no more hardcoded locale list)
- Update docs/docs/faq.md and user-guide/user-profile.md language lists

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## [0.28.0](v0.27.7...v0.28.0) (2026-05-19)

### Features

* **01-01:** add partial unique index for ReviewRequest PENDING uniqueness ([8f50415](8f50415))
* **01-01:** add Review & Approval data model to schema.zmodel ([48fd7d0](48fd7d0))
* **01-02:** add ReviewGateError + AlreadyPendingError classes and detectors ([897b23b](897b23b))
* **01-03:** add assertReviewGatePasses preflight helper ([97edf57](97edf57)), closes [9/#10](https://github.com/9/testplanit/issues/10)
* **01-04:** wire review gate into bulk-edit route per-case loop ([0cd5b77](0cd5b77))
* **01-04:** wire review gate into milestoneActions + submit-result ([6903820](6903820))
* **01-04:** wire review gate into ZenStack auto-API handler ([3e7b10d](3e7b10d))
* **02-01:** add Projects.reviewWorkflowEnabled + extend three gate @[@deny](https://github.com/deny) rules ([c23a835](c23a835))
* **02-01:** IneligibleReviewerError class + symmetric AlreadyPending catches ([ff50677](ff50677))
* **02-02:** add decideReviewRequest service with app-layer role-eligibility check (D-16) ([820b7c6](820b7c6)), closes [#2](#2)
* **02-02:** add POST /api/reviews/[id]/decide route wiring decideReviewRequest ([756632a](756632a))
* **02-02:** extend assertReviewGatePasses with D-20 feature-flag short-circuits ([0e3b1d4](0e3b1d4))
* **02-03:** add GET /api/config/review-feature route exposing system flag ([202eb24](202eb24))
* **02-03:** add useReviewFeatureEnabled hook composing system + project flags ([815e9d9](815e9d9))
* **02-04:** add AssigneeCombobox for D-03 user+role picker ([ebece46](ebece46))
* **02-04:** add RequestReviewButton with D-02 visibility predicate ([35ab875](35ab875))
* **02-04:** add RequestReviewSheet for REQUESTER-02 / REQUESTER-03 ([eec53ad](eec53ad))
* **02-05:** add CancelRequestButton (AlertDialog + status mutation) ([58ff202](58ff202))
* **02-05:** add PendingReviewBadge (stateless cell helper) ([15997d2](15997d2))
* **02-05:** add ReviewStatusBanner with PENDING/CHANGES_REQUESTED/REJECTED branches ([b9025e0](b9025e0))
* **02-06:** add ReviewActionPanel (sticky cluster + auto-detect visibility) ([a3916f8](a3916f8))
* **02-06:** add ReviewDecisionDialogs (Approve, RequestChanges, Reject) ([45445b8](45445b8))
* **02-06:** add useEffectiveRoleOnProject custom hook ([7621c61](7621c61))
* **02-07:** add ReviewInboxButton (icon Link + pending count Badge) ([1e75483](1e75483))
* **02-07:** mount ReviewInboxButton in global Header ([0468dcb](0468dcb))
* **02-08:** implement /reviews inbox page (REVIEWER-01 + D-09/D-10/D-20) ([a975fac](a975fac))
* **02-09:** mount review surfaces on entity detail pages ([8aa050e](8aa050e))
* **02-09:** wire PendingReviewBadge into Cases/Runs/Sessions list views ([e36f108](e36f108))
* **02-10:** add Advanced project settings tab with reviewWorkflowEnabled toggle ([e3e4bcf](e3e4bcf))
* **02-10:** add requiresReview Switch to EditWorkflow (Phase 1 deferred UI) ([043ba1f](043ba1f)), closes [#1](#1)
* **02-10:** add SystemFeatureCard read-only display + wire into admin/workflows ([16a1369](16a1369))
* **02-11:** narrow CR-01 TOCTOU window in auto-API review-gate preflight ([5d788aa](5d788aa))

### Bug Fixes

* **01-06:** handle Prisma 6 array meta.target + guard window in setup ([ead3317](ead3317))
* **02-04:** import beforeEach from vitest in plan 02-04 test files ([a38ac1f](a38ac1f))
* **02-review:** annotate r.entityId map callbacks to clear implicit any ([13b73ca](13b73ca))
* **02-review:** CR-01 close TOCTOU race in decideReviewRequest ([7d29c3c](7d29c3c))
* **02-review:** CR-02 require caller auth on /api/get-user-permissions ([b983e33](b983e33))
* **02-review:** CR-03 tighten auto-API gate entityId/stateId extraction ([c923ae3](c923ae3))
* **02-review:** CR-04 pre-stamp consumedAt in auto-API gate tx ([29fe271](29fe271))
* **02-review:** WR-01 dialog ineligibility matches typed code, not status ([36e213f](36e213f))
* **02-review:** WR-02 use negative sentinel projectId in feature-enabled hook ([79eab81](79eab81))
* **02-review:** WR-03 block self-assignment in RequestReviewSheet ([cead3e9](cead3e9))
* **02-review:** WR-06 shorten useEffectiveRoleOnProject staleTime ([5a67065](5a67065))
* **02-review:** WR-08 audit fallback for nullable apiToken.name ([db19680](db19680))
* **02-review:** WR-09 clip oversized decisionComment in banner + cap input ([da1a2a7](da1a2a7))
* **i18n:** add Turkish (tr-TR) and Russian (ru-RU) locales ([#318](#318)) ([a089676](a089676))
…nts (#319)

release-please auto-generated the v0.28.0 section based on commit history
and listed ~30 review-approval features whose source code was reverted in
PR #317 before the release. None of those features actually shipped in
v0.28.0. The GitHub release page has already been corrected; this commit
brings the in-repo CHANGELOG.md in line so future release-please runs
have a clean baseline.

The actual v0.28.0 contents are Turkish/Russian locale support (#318),
the shared queue presets (#316), and the cleanup itself (#317).

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Brings the v0.28.0 i18n release (Turkish + Russian locales, queue-presets
refactor, polluted-CHANGELOG cleanup) onto the parameterized-test-cases
branch so it merges as v0.29.0.

Conflict resolutions:

- testplanit/CHANGELOG.md, docs/docs/{faq,user-guide/user-profile}.md:
  took main (cleaned v0.28.0 entry + 15-locale list).
- testplanit/{schema.zmodel,prisma/schema.prisma}: union — added
  tr_TR and ru_RU to the Locale enum, kept the ITERATION_* AuditAction
  values from this branch.
- testplanit/i18n/{navigation,dateFnsLocales}.ts, testplanit/crowdin.yml:
  union of locale lists.
- testplanit/package.json: main's 0.28.0 version + branch's
  worker:iteration-generation script + the fix-zenstack-symlink.js call
  in `generate`.
- testplanit/lib/queues.ts, queueNames.ts, ecosystem.config.js,
  scripts/build-workers.js: registered the iteration-generation queue
  and worker alongside main's new STANDARD_RETRY / NO_RETRY_*
  JobsOptions constants from #316.
- 13 messages/*.json locale files: branch's parameter strings are pure
  additions over main's content, so HEAD wins for every conflict block.
- testplanit/app/api/users/[userId]/route.ts: took main's refactor to
  shared Zod enum constants (Theme, Locale, ItemsPerPage, DateFormat,
  TimeFormat) so the new locales flow through automatically.
- testplanit/app/api/llm/generate-test-cases/{expand,outline,shared}.ts:
  kept branch's includeParameters admin gate + buildSystemPrompt
  signature extension.
- testplanit/lib/services/testRunSummary*.ts and
  webhooks/event-emitters/testRunEvents.{ts,test.ts}: kept branch's
  getPerCaseIterationCounts + per-iteration redacted-values
  assembly (D-13 / INT-03 / INT-04).
- testplanit/app/[locale]/projects/settings/[projectId]/webhooks/
  webhook-outbound-form.tsx: kept the iteration.result.recorded event
  + the "result.recorded" → resultRecorded i18n path mapping.
- Various MCP e2e specs + admin pages: took main's
  eslint-disable-next-line comments (silence no-console / no-var /
  react-hooks/refs warnings that the branch lost).
- testplanit/lib/hooks/__model_meta.ts +
  testplanit/lib/openapi/zenstack-openapi.json: regenerated via
  `pnpm generate` after schema resolution.
- docs/blog/2026-05-18-introducing-parameterized-test-cases.md:
  v0.28.0 → v0.29.0 (release will follow this branch's merge to main.

Verified: undefined
 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL  Command "lint" not found (0 errors), undefined
 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL  Command "type-check" not found, undefined
 ERR_PNPM_RECURSIVE_EXEC_FIRST_FAIL  Command "build" not found all
clean on the merged tree.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
EOF
)
@therealbrad therealbrad merged commit 65123cf into releases/v0.29 May 19, 2026
@therealbrad therealbrad deleted the features/parameterized-test-cases branch May 19, 2026 18:47
therealbrad added a commit that referenced this pull request May 19, 2026
Establishes the parent linkage between releases/v0.29 and main that was
lost when PR #320 squashed the parameterized-test-cases branch into a
single commit. The squashed commit already contained main's v0.28.0
work (i18n release + queue presets + CHANGELOG cleanup), so the only
conflicts were in three locale JSON files where main's bare entries
collided with the branch's parameterized strings.

Conflict resolutions (all in testplanit/messages/):

- de-DE.json: HEAD adds the datasets key main does not have. Took HEAD.
- ru-RU.json, tr-TR.json: main added these as bare locale stubs in
  PR #318; the squashed commit on this branch already carries them
  populated with the parameterized strings. Took HEAD for both — net
  result is identical to HEAD, so this commit is a parent-linkage
  merge with no tree changes.

Verified with pnpm crowdin:sync (no diff against the resolved tree)
and pnpm lint (0 errors).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant