Compare commits

...
Author SHA1 Message Date
james.maes e333223470 Merge pull request #48 from QRun-IO/feature/GH-712-next-rc12
chore(#712): prepare Next UI 1.0.0-RC.12
2026-10-11 12:03:09 -05:00
james.maesandClaude Opus 5.5 a4c8eaf8c9 chore(#712): prepare Next UI 1.0.0-RC.12
Version 1.0.0-RC.12 (package.json, pom revision), RC12 preparation notes
collecting everything merged since RC11 (PR25-PR46, incl. the #45
always-available record Actions menu), reset unreleased notes, and updated
version references in README, guides and the testing policy. Backend
requirement moves to published QQQ 4.1 candidates (RC.1+, RC.2+ for
standalone branding images, QQQ #1009).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 10:52:03 -05:00
james.maes b0b0d469fe Merge pull request #46 from QRun-IO/fix/45-record-actions-menu
fix(record-view): always show the Actions menu when it has entries (#45)
2026-10-11 10:04:06 -05:00
james.maesandClaude Opus 5.5 3ceed91214 test(acceptance): PRC-050 record menu also offers Audit (#45)
The default record Actions menu ends with Audit for users who can read
audits, so assert Developer Mode and Audit are present rather than
Developer Mode being last.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 08:58:42 -05:00
james.maesandClaude Opus 5.5 4e12a5d524 fix(record-view): always show the Actions menu when it has entries (#45)
RecordViewHeader passed an empty entry list to the desktop RecordActions
unless the table had processes or a custom VIEW_SCREEN_ACTIONS menu, so
plain tables lost Developer Mode and Audit. The Actions controls (desktop
and phone) were also gated on write permissions or processes, hiding them
from read-only tables that still offer Developer Mode and Audit.

Pass the resolved default/custom menu entries through unchanged and show
the Actions controls whenever that menu has items. RecordActions keeps
de-duplicating Copy/Delete via menuOffersAction, so those now live in the
menu instead of also appearing as buttons, matching Material.

Update the component tests and the PRC-050 / REC-046 acceptance specs
that asserted the old missing-menu behavior.

Closes #45

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 07:53:46 -05:00
14 changed files with 209 additions and 86 deletions

No files matched your search

+3 -3
View File
@@ -2,7 +2,7 @@
A metadata-driven admin UI for the QQQ low-code application framework, rewritten from React + Material UI to Next.js 16 + Tailwind CSS + shadcn/ui. The UI renders entirely from backend metadata — no table names, field lists, or navigation items are hardcoded in the frontend.
**This checkout targets Next UI 1.0.0-RC.11**, with searchable-selector and script-completion input-race corrections. Confirm availability in the [RC11 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11) before installing; see the [RC11 notes](docs/releases/1.0.0-RC.11.md) for the preparation checkpoint and verification scope. [RC10](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.10) was the previously verified public candidate at that checkpoint and remains immutable. Final 1.0 remains held for application soak and explicit owner approval.
**This checkout targets Next UI 1.0.0-RC.12**, with the always-available record Actions menu, private query-state clearing between users, standalone branding and CSP, grid focus and other fixes. Confirm availability in the [RC12 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.12) before installing; see the [RC12 notes](docs/releases/1.0.0-RC.12.md) for the preparation checkpoint and verification scope. [RC11](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11) was the previously verified public candidate at that checkpoint and remains immutable. Final 1.0 remains held for application soak and explicit owner approval.
**Current roadmap and known issues:** [Next UI 1.0 tracker](https://github.com/QRun-IO/qqq/issues/713). GitHub issues are the source of truth for status and remaining work; see [issue tracking](docs/ISSUE-TRACKING.md).
@@ -14,11 +14,11 @@ The dashboard is the default admin UI for QQQ 4.1+. Add the jar (managed by `qqq
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.11</version>
<version>1.0.0-RC.12</version>
</dependency>
```
The explicit version overrides an older BOM-managed frontend. RC11 requires the QQQ 4.1.0-SNAPSHOT development backend including PRs #913 and #908; see the release notes for the verified backend commit. Select Next explicitly with `-Dqqq.javalin.frontend=next`. This does not release QQQ 4.1.
The explicit version overrides an older BOM-managed frontend. RC12 requires a published QQQ 4.1 candidate: 4.1.0-RC.1 or later, and 4.1.0-RC.2 or later for standalone branding images (QQQ #1009); see the release notes for the verified backend. Select Next explicitly with `-Dqqq.javalin.frontend=next`. This does not release QQQ 4.1.
The jar holds the static export (`pnpm build:export`) under `next-dashboard/`. Deep links such as `/app/person/1` are served from placeholder pages and read their route from the browser path, so no Node.js server is involved. The Material Dashboard remains available: select it with `withServeFrontendMaterialDashboard(true)` or `-Dqqq.javalin.frontend=material`. Material routes are `/<app>/<table>/<id>`; Next routes are `/app/<table>/<id>`.
+2 -2
View File
@@ -1,10 +1,10 @@
# Next UI agent guide
> **This checkout targets RC11.** Confirm availability in the [RC11 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11) before installing. [RC11 notes](../releases/1.0.0-RC.11.md) record the preparation checkpoint and verification scope; RC10 was the previously verified public candidate at that checkpoint. Earlier versioned receipts remain historical evidence; final 1.0 remains held.
> **This checkout targets RC12.** Confirm availability in the [RC12 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.12) before installing. [RC12 notes](../releases/1.0.0-RC.12.md) record the preparation checkpoint and verification scope; RC11 was the previously verified public candidate at that checkpoint. Earlier versioned receipts remain historical evidence; final 1.0 remains held.
> **Live roadmap and known issues:** [QRun-IO/qqq#713](https://github.com/QRun-IO/qqq/issues/713). GitHub issues own current status and remaining work. Follow the [issue-tracking policy](../ISSUE-TRACKING.md) when reporting or resolving findings; keep this guide focused on usage and evidence.
This is an implementation playbook for coding agents working on the QQQ Next UI. Read the [developer guide](./next-ui-developer-guide.md) for user and integrator behavior, the repository [AGENTS.md](../../AGENTS.md) and [CLAUDE.md](../../CLAUDE.md) for scoped conventions, and the [real-server feature matrix](../acceptance/feature-matrix.md) plus [Material parity ledger](../acceptance/material-parity.md) before claiming compatibility. This guide reflects the `feature/next-1.0` release worktree as of 2026-10-01; the previously verified public candidate at the RC11 preparation checkpoint was [1.0.0-RC.10](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.10) at `12cf774`, and final `1.0.0` remains on hold. Later branch changes must not be attributed to that immutable release. Some ledger entries predate integrated changes, so reconcile them with source and real-server evidence. A green subset of tests is evidence for that subset, not proof of complete Material parity or a published 1.0 release.
This is an implementation playbook for coding agents working on the QQQ Next UI. Read the [developer guide](./next-ui-developer-guide.md) for user and integrator behavior, the repository [AGENTS.md](../../AGENTS.md) and [CLAUDE.md](../../CLAUDE.md) for scoped conventions, and the [real-server feature matrix](../acceptance/feature-matrix.md) plus [Material parity ledger](../acceptance/material-parity.md) before claiming compatibility. This guide reflects the `feature/next-1.0` release worktree as of 2026-10-01; the previously verified public candidate at the RC12 preparation checkpoint was [1.0.0-RC.11](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11) at `2853b9b`, and final `1.0.0` remains on hold. Later branch changes must not be attributed to that immutable release. Some ledger entries predate integrated changes, so reconcile them with source and real-server evidence. A green subset of tests is evidence for that subset, not proof of complete Material parity or a published 1.0 release.
## Operating rule: metadata is the product boundary
+4 -4
View File
@@ -1,12 +1,12 @@
# Next UI developer guide
> **This checkout targets RC11.** Confirm availability in the [RC11 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11) before installing. [RC11 notes](../releases/1.0.0-RC.11.md) record the preparation checkpoint and verification scope; RC10 was the previously verified public candidate at that checkpoint. Earlier versioned receipts remain historical evidence; final 1.0 remains held.
> **This checkout targets RC12.** Confirm availability in the [RC12 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.12) before installing. [RC12 notes](../releases/1.0.0-RC.12.md) record the preparation checkpoint and verification scope; RC11 was the previously verified public candidate at that checkpoint. Earlier versioned receipts remain historical evidence; final 1.0 remains held.
> **RC9:** application code is unchanged from RC8. See [RC9 notes](../releases/1.0.0-RC.9.md) and [published releases](https://github.com/QRun-IO/qqq-frontend-next/releases) for versioned verification and public delivery status. Earlier RC8 receipts below remain historical evidence.
> **Live roadmap and known issues:** [QRun-IO/qqq#713](https://github.com/QRun-IO/qqq/issues/713). GitHub issues own current status and remaining work. Follow the [issue-tracking policy](../ISSUE-TRACKING.md) when reporting or resolving findings; keep this guide focused on usage and evidence.
This guide describes the `feature/next-1.0` release worktree as of 2026-10-01. It explains the current frontend and backend metadata contract. The previously verified public candidate at the RC11 preparation checkpoint was [1.0.0-RC.10](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.10) (commit `12cf774`); final `1.0.0` remains on hold. Later branch changes are not retroactive changes to that immutable release. For behavior proven against a real server use the [feature matrix](../acceptance/feature-matrix.md); for the full Material inventory use the [parity ledger](../acceptance/material-parity.md). Some ledger rows predate recent work, so verify an individual status against current source and acceptance results before making a release claim.
This guide describes the `feature/next-1.0` release worktree as of 2026-10-01. It explains the current frontend and backend metadata contract. The previously verified public candidate at the RC12 preparation checkpoint was [1.0.0-RC.11](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11) (commit `2853b9b`); final `1.0.0` remains on hold. Later branch changes are not retroactive changes to that immutable release. For behavior proven against a real server use the [feature matrix](../acceptance/feature-matrix.md); for the full Material inventory use the [parity ledger](../acceptance/material-parity.md). Some ledger rows predate recent work, so verify an individual status against current source and acceptance results before making a release claim.
The visual baseline is the September 25 Next UI design (`e42ad2b2`). Material remains the reference for backend contracts and functional behavior. The layout defaults below describe current source; they do not certify a completed visual review.
@@ -35,7 +35,7 @@ The preferred QQQ-hosted distribution is the `com.kingsrook.qqq:qqq-frontend-nex
<dependency>
<groupId>com.kingsrook.qqq</groupId>
<artifactId>qqq-frontend-next</artifactId>
<version>1.0.0-RC.11</version>
<version>1.0.0-RC.12</version>
</dependency>
```
@@ -48,7 +48,7 @@ pnpm build:export
pnpm build:jar
```
The mock setting is for isolated development. Set `NEXT_PUBLIC_MOCK_API=false` and connect a real QQQ backend for integration. `build:export` writes `out/`; `build:jar` packages it with Maven. The Maven POM enforces the presence of static placeholder pages. The default `pnpm build` instead creates a standalone Node server, whose `QQQ_BACKEND_URL` build setting adds same-origin rewrites. For the container path see [`docker/quickstart/Dockerfile`](../../docker/quickstart/Dockerfile). This checkout targets `1.0.0-RC.11`, overriding an older BOM-managed frontend; confirm its availability in the [RC11 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11) before installing. It requires the QQQ 4.1.0-SNAPSHOT development backend including PRs #913 and #908; see the release notes for its verified backend commit. Use a matching local backend/frontend build to test later branch changes.
The mock setting is for isolated development. Set `NEXT_PUBLIC_MOCK_API=false` and connect a real QQQ backend for integration. `build:export` writes `out/`; `build:jar` packages it with Maven. The Maven POM enforces the presence of static placeholder pages. The default `pnpm build` instead creates a standalone Node server, whose `QQQ_BACKEND_URL` build setting adds same-origin rewrites. For the container path see [`docker/quickstart/Dockerfile`](../../docker/quickstart/Dockerfile). This checkout targets `1.0.0-RC.12`, overriding an older BOM-managed frontend; confirm its availability in the [RC12 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.12) before installing. It requires a published QQQ 4.1 candidate (4.1.0-RC.1 or later; 4.1.0-RC.2 or later for standalone branding images, QQQ #1009); see the release notes for the verified backend. Use a matching local backend/frontend build to test later branch changes.
| Setting | Behavior in this checkout |
|---|---|
+2 -2
View File
@@ -10,11 +10,11 @@ RC1 remains immutable: [v1.0.0-RC.1](https://github.com/QRun-IO/qqq-frontend-nex
The owner authorizes publishing new RCs at verified stability or daily milestones. Each RC gets a new immutable version and notes describing its fixes, backend prerequisites, verification, and known gaps. RC publication does not authorize final 1.0.
Previously verified public candidate at the RC11 preparation checkpoint: **[1.0.0-RC.10](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.10)**. Its public release entry records exact artifact verification, backend requirements and known limits. RC1 through RC9 remain available and unchanged.
Previously verified public candidate at the RC12 preparation checkpoint: **[1.0.0-RC.11](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11)**. Its public release entry records exact artifact verification, backend requirements and known limits. RC1 through RC10 remain available and unchanged.
## Fixes during testing
This checkout targets [RC11](1.0.0-RC.11.md); confirm availability in the [RC11 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11) before installing. The candidate adds searchable-selector and script-completion input-race corrections from [PR24](https://github.com/QRun-IO/qqq-frontend-next/pull/24). These changes are absent from immutable RC10. Candidate notes distinguish versioned local checks from public delivery and retain the historical browser failures and remaining RC limits. Local previews may intentionally preserve an earlier version.
This checkout targets [RC12](1.0.0-RC.12.md); confirm availability in the [RC12 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.12) before installing. The candidate adds the always-available record Actions menu ([#45](https://github.com/QRun-IO/qqq-frontend-next/issues/45)), private query-state clearing between users (QQQ #1006), standalone branding and CSP derivation (QQQ #1009, #1017), grid and report focus fixes, and the other changes listed in the RC12 notes. These changes are absent from immutable RC11. Candidate notes distinguish versioned local checks from public delivery and retain the historical browser failures and remaining RC limits. Local previews may intentionally preserve an earlier version.
RC9's immutable-tag acceptance run completed with known Firefox and tablet failures; its release notes preserve the exact results. Follow [qqq#985](https://github.com/QRun-IO/qqq/issues/985) for browser investigations and [qqq#713](https://github.com/QRun-IO/qqq/issues/713) for the complete backlog. The [original Next visual review](../acceptance/next-ui-visual-review.md) and owner approval remain separate requirements.
+60
View File
@@ -0,0 +1,60 @@
# Next UI 1.0.0-RC.12 — preparation
At this preparation checkpoint, RC12 is being prepared for application testing and **is not yet published**. [RC11](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11) remains the latest verified public candidate and is immutable. Planned artifacts are `com.kingsrook.qqq:qqq-frontend-next:1.0.0-RC.12` and `ghcr.io/qrun-io/qqq-frontend-next:1.0.0-RC.12`. Public hashes, signatures and image digests will be recorded after delivery verification. Final 1.0 remains held for application soak and explicit owner approval.
## Source
Main at `b0b0d469feb0b1bd473fbf189540e712ffc09807` (merge of [PR46](https://github.com/QRun-IO/qqq-frontend-next/pull/46)). Merged since RC11: PR25, PR27, PR28, PR29, PR30, PR32, PR35, PR36, PR38, PR40, PR42, PR43, PR44 and PR46. The SQL Workbench work in draft PR47 is not included.
## Backend requirements
RC12 runs on a published QQQ 4.1 candidate: **4.1.0-RC.1** or later. Standalone branding images (below) need **4.1.0-RC.2** or later, which contains QQQ #1009's `/qqq/branding/logo` and `/qqq/branding/icon` endpoints. RC12 does not publish QQQ 4.1.
## Product fixes
- Always show the record view's Actions menu when it has entries. Tables without processes or a custom `VIEW_SCREEN_ACTIONS` menu, and read-only tables, again reach Developer Mode and Audit (Material parity); Copy and Delete move into that menu instead of also appearing as separate buttons. [#45](https://github.com/QRun-IO/qqq-frontend-next/issues/45)
- Clear remembered query filters and saved-view selections on logout or a detected user identity change. Same-user resumes keep their query state; global appearance, density and legacy column preferences remain intact. This correction awaits its own branch acceptance. [#1006](https://github.com/QRun-IO/qqq/issues/1006)
- In searchable possible-value fields, invalidate old results immediately when the search term changes, so a debounced request cannot remove an option during its selection. [#970](https://github.com/QRun-IO/qqq/issues/970)
- Keep code suggestions open when a delayed textarea scroll event reports the position already used for the popup; dismiss them when the scroll position actually changes. [#954](https://github.com/QRun-IO/qqq/issues/954)
- Preserve focus on the search dialog's Clear control when keyboard interaction occurs before its scheduled initial focus. [#950](https://github.com/QRun-IO/qqq/issues/950)
- Preserve a clicked possible-value option in WebKit when blurring its search input would scroll the form between mousedown and mouseup. The selected value now reaches process submission and generated output. [#978](https://github.com/QRun-IO/qqq/issues/978)
- Keep authentication loading until explicit logout finishes, so a late logout response cannot erase cookies from a subsequent successful sign-in. [#984](https://github.com/QRun-IO/qqq/issues/984)
## Standalone branding and CSP (QQQ #1009, #1017)
The standalone Node server can route the exact root-relative `branding.logo` and
`branding.icon` paths published by authentication metadata through fixed backend
image roles. Local Node `public` files keep precedence; external image URLs and
Java dashboard serving are unchanged. This does not expose the whole application
overlay or add a general proxy.
This requires **a QQQ middleware-javalin build containing QQQ #1009's
`/qqq/branding/logo` and `/qqq/branding/icon` endpoints**. They are absent from
published **4.1.0-RC.1**. The minimum numbered release is not yet assigned; a
source build must record the exact backend commit/JAR hashes. Older deployments
must supply branding through their Node `public` files or an existing external
image URL. No automatic compatibility with old relative backend assets is claimed.
The preserved standalone qualification against public RC1 remains **5 passed,
38 failed** out of 43 cases because of missing branding assets. The original
staging failure is also retained. The corrected cross-repository pairing has
not yet received its browser qualification; neither #1009 nor #734 is closed by
focused unit/HTTP results.
## Browser testing and infrastructure
- Pin the official Playwright runner containing the fixed Linux WebKit network library, and retain native browser diagnostics for navigation failures. The previous bundle's heap corruption was reproduced; migration to a stable runner remains tracked separately. [#904](https://github.com/QRun-IO/qqq/issues/904), [#973](https://github.com/QRun-IO/qqq/issues/973)
- Correlate interrupted Firefox font diagnostics with real request and decode evidence, wait for initial fonts before the deliberate redirect, and observe transient feedback during the action that produces it. [#951](https://github.com/QRun-IO/qqq/issues/951), [#953](https://github.com/QRun-IO/qqq/issues/953), [#967](https://github.com/QRun-IO/qqq/issues/967)
- Wait for nested report metadata and the destination's owned iframe before advancing acceptance navigation. [#961](https://github.com/QRun-IO/qqq/issues/961), [#972](https://github.com/QRun-IO/qqq/issues/972)
- Verify Linux WebKit's downloaded filename normalization while retaining file-content and cleanup assertions. [#979](https://github.com/QRun-IO/qqq/issues/979)
- Verify actual browser refusal of disallowed framing with a control that renders when the protective headers are removed. [#981](https://github.com/QRun-IO/qqq/issues/981)
- Recognize Firefox's network-error diagnostic for the gallery's intentionally missing extension only after that same script request returns HTTP 404; preserve the raw evidence and reject unrelated failures. [#983](https://github.com/QRun-IO/qqq/issues/983)
- Select all ten ESB scenarios for both phone and tablet acceptance, retaining real broker delivery, lifecycle, replay, SQL and permission checks. Matrix references now use the central QQQ tickets. [#986](https://github.com/QRun-IO/qqq/issues/986), [#990](https://github.com/QRun-IO/qqq/issues/990)
## Verification checkpoint
PR checks on the RC12 preparation source (typecheck, lint, unit tests and coverage, mocked e2e, bundle budgets, Storybook, image builds and the full real-backend acceptance workflow) are recorded on the preparation PR. Tag acceptance and public-artifact verification are recorded in the [RC12 GitHub release](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.12) after delivery. No RC11 test count or artifact digest is attributed to RC12.
## Remaining RC limits
The RC11 limits carry forward unchanged. The RC11 tag acceptance recorded 2,990 passes and 2 owner-accepted failures (Firefox NAV-014 font diagnostic, tablet NAV-056 access-control diagnostics) under the documented secondary-browser/tablet exception ([#985](https://github.com/QRun-IO/qqq/issues/985), [#798](https://github.com/QRun-IO/qqq/issues/798)); it does not waive new product defects or authorize GA. Historical REC-054/PRC-061 observations remain under [#954](https://github.com/QRun-IO/qqq/issues/954) and [#970](https://github.com/QRun-IO/qqq/issues/970). Material parity, sub-path hosting, real Auth0/Drive/QuickSight verification, visual review and application soak remain tracked in [#713](https://github.com/QRun-IO/qqq/issues/713), [#711](https://github.com/QRun-IO/qqq/issues/711), [#712](https://github.com/QRun-IO/qqq/issues/712) and [#904](https://github.com/QRun-IO/qqq/issues/904). The image backend origin remains fixed at build time to `host.docker.internal:8000`; use the JAR or rebuild for another origin.
+3 -43
View File
@@ -1,53 +1,13 @@
# Next UI — unreleased
[RC11 is published and immutable](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11). It includes the searchable-selector and script-completion corrections from [PR24](https://github.com/QRun-IO/qqq-frontend-next/pull/24); its release details remain in [RC11 notes](1.0.0-RC.11.md). Current main also contains the later menu, copy-focus and grid fixes, which are not in that published artifact. Central tracking: [qqq#713](https://github.com/QRun-IO/qqq/issues/713), [browser investigations #985](https://github.com/QRun-IO/qqq/issues/985), and [QQQ Roadmap Project 12](https://github.com/orgs/QRun-IO/projects/12).
[RC12 preparation](1.0.0-RC.12.md) collects every change merged since [RC11](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.11): the always-available record Actions menu ([#45](https://github.com/QRun-IO/qqq-frontend-next/issues/45)), private query-state clearing between users, standalone branding and CSP derivation, grid and report focus fixes, and browser-testing hardening. RC11 is published and immutable. Changes merged after the RC12 preparation commit are listed below. Central tracking: [qqq#713](https://github.com/QRun-IO/qqq/issues/713), [browser investigations #985](https://github.com/QRun-IO/qqq/issues/985), and [QQQ Roadmap Project 12](https://github.com/orgs/QRun-IO/projects/12).
## Product fixes
- Clear remembered query filters and saved-view selections on logout or a detected user identity change. Same-user resumes keep their query state; global appearance, density and legacy column preferences remain intact. This correction awaits its own branch acceptance. [#1006](https://github.com/QRun-IO/qqq/issues/1006)
- In searchable possible-value fields, invalidate old results immediately when the search term changes, so a debounced request cannot remove an option during its selection. [#970](https://github.com/QRun-IO/qqq/issues/970)
- Keep code suggestions open when a delayed textarea scroll event reports the position already used for the popup; dismiss them when the scroll position actually changes. [#954](https://github.com/QRun-IO/qqq/issues/954)
- Preserve focus on the search dialog's Clear control when keyboard interaction occurs before its scheduled initial focus. [#950](https://github.com/QRun-IO/qqq/issues/950)
- Preserve a clicked possible-value option in WebKit when blurring its search input would scroll the form between mousedown and mouseup. The selected value now reaches process submission and generated output. [#978](https://github.com/QRun-IO/qqq/issues/978)
- Keep authentication loading until explicit logout finishes, so a late logout response cannot erase cookies from a subsequent successful sign-in. [#984](https://github.com/QRun-IO/qqq/issues/984)
None yet.
## Browser testing and infrastructure
- Pin the official Playwright runner containing the fixed Linux WebKit network library, and retain native browser diagnostics for navigation failures. The previous bundle's heap corruption was reproduced; migration to a stable runner remains tracked separately. [#904](https://github.com/QRun-IO/qqq/issues/904), [#973](https://github.com/QRun-IO/qqq/issues/973)
- Correlate interrupted Firefox font diagnostics with real request and decode evidence, wait for initial fonts before the deliberate redirect, and observe transient feedback during the action that produces it. [#951](https://github.com/QRun-IO/qqq/issues/951), [#953](https://github.com/QRun-IO/qqq/issues/953), [#967](https://github.com/QRun-IO/qqq/issues/967)
- Wait for nested report metadata and the destination's owned iframe before advancing acceptance navigation. [#961](https://github.com/QRun-IO/qqq/issues/961), [#972](https://github.com/QRun-IO/qqq/issues/972)
- Verify Linux WebKit's downloaded filename normalization while retaining file-content and cleanup assertions. [#979](https://github.com/QRun-IO/qqq/issues/979)
- Verify actual browser refusal of disallowed framing with a control that renders when the protective headers are removed. [#981](https://github.com/QRun-IO/qqq/issues/981)
- Recognize Firefox's network-error diagnostic for the gallery's intentionally missing extension only after that same script request returns HTTP 404; preserve the raw evidence and reject unrelated failures. [#983](https://github.com/QRun-IO/qqq/issues/983)
- Select all ten ESB scenarios for both phone and tablet acceptance, retaining real broker delivery, lifecycle, replay, SQL and permission checks. Matrix references now use the central QQQ tickets. [#986](https://github.com/QRun-IO/qqq/issues/986), [#990](https://github.com/QRun-IO/qqq/issues/990)
## Verification and remaining work
Current main `5ca3e0b` has a complete [3,010-case first-attempt acceptance pass](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36989591498): 620 per desktop browser and 575 per touch profile. That run qualifies the main tree before the query-state correction above. The earlier checkpoints below are retained separately.
The [combined acceptance run](https://github.com/QRun-IO/qqq-frontend-next/actions/runs/36942612338) on head `7ff90f1` (the same tree as merge `289eca19`) passed all 2,972 executed cases: 614 each in Chromium, Firefox and WebKit, and 565 each on phone and tablet. All desktop gates covered 401 required rows. The workflow still failed its touch coverage gate because ten ESB rows were not selected; that original failure remains retained under [#990](https://github.com/QRun-IO/qqq/issues/990). After adding the selection markers, a separate native Linux run passed all 20 ESB phone/tablet cases, with zero retries, skips, flaky outcomes or report errors. Its frozen frontend export is unchanged; its freshly rebuilt backend is `55b33e6`. Complete touch collection is 575 cases per profile. The subsequent [complete PR23 outcome](https://github.com/QRun-IO/qqq/issues/990#issuecomment-5944836159) covers all 401 required IDs on each profile with zero missing and all 20 ESB touch cases passing, completing #990. Touch totals are 1,149 passes and one unrelated tablet NAV-056 failure; the full gate and workflow remain failed. [NAV-056 investigation](https://github.com/QRun-IO/qqq/issues/985#issuecomment-5944830383) remains separate, with no shared-cause claim or new waiver.
Each linked issue and PR records the source commit, reproduction and scoped verification. Preserve failed full-run snapshots separately from later focused passes. The broader historical navigation inventory [#904](https://github.com/QRun-IO/qqq/issues/904), same-user session stall [#952](https://github.com/QRun-IO/qqq/issues/952), legacy process-navigation stall [#960](https://github.com/QRun-IO/qqq/issues/960), and original toast-paint uncertainty [#967](https://github.com/QRun-IO/qqq/issues/967) remain open. The [RC10 preparation notes](1.0.0-RC.10.md) retain their historical checkpoint; its [public release entry](https://github.com/QRun-IO/qqq-frontend-next/releases/tag/v1.0.0-RC.10) records final delivery verification. See [RC11 notes](1.0.0-RC.11.md) for the successor candidate. No final-release approval is claimed by these results.
None yet.
Final 1.0 remains held for real-application soak and explicit owner approval under [#712](https://github.com/QRun-IO/qqq/issues/712). This work does not authorize a QQQ 4.1 release. See the [testing-period policy](1.0-testing.md) and [issue lifecycle](../ISSUE-TRACKING.md).
## Pending standalone branding compatibility (QQQ #1009)
The standalone Node server can route the exact root-relative `branding.logo` and
`branding.icon` paths published by authentication metadata through fixed backend
image roles. Local Node `public` files keep precedence; external image URLs and
Java dashboard serving are unchanged. This does not expose the whole application
overlay or add a general proxy.
This requires **a QQQ middleware-javalin build containing QQQ #1009's
`/qqq/branding/logo` and `/qqq/branding/icon` endpoints**. They are absent from
published **4.1.0-RC.1**. The minimum numbered release is not yet assigned; a
source build must record the exact backend commit/JAR hashes. Older deployments
must supply branding through their Node `public` files or an existing external
image URL. No automatic compatibility with old relative backend assets is claimed.
The preserved standalone qualification against public RC1 remains **5 passed,
38 failed** out of 43 cases because of missing branding assets. The original
staging failure is also retained. The corrected cross-repository pairing has
not yet received its browser qualification; neither #1009 nor #734 is closed by
focused unit/HTTP results.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "qqq-frontend-next",
"version": "1.0.0-RC.11",
"version": "1.0.0-RC.12",
"private": true,
"packageManager": "pnpm@9.15.9",
"scripts": {
+1 -1
View File
@@ -47,7 +47,7 @@
</developers>
<properties>
<revision>1.0.0-RC.11</revision>
<revision>1.0.0-RC.12</revision>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.build.outputTimestamp>2026-09-24T00:00:00Z</project.build.outputTimestamp>
</properties>
@@ -16,12 +16,13 @@
// Phone action sheet: focus management and routes (QRun-IO/qqq#694, #649)
import React from 'react'
import { render, screen, waitFor } from '@testing-library/react'
import { render, screen, waitFor, within } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { beforeEach, describe, expect, it, vi } from 'vitest'
import type { QProcessMetaData, QTableMetaData } from '@/types'
import type { AuditSource } from '@/lib/api/audits'
import type { RecordViewActionsPlacement } from '@/lib/utils/record-layout-utils'
import { RecordViewHeader } from './RecordViewHeader'
@@ -144,20 +145,92 @@ describe('RecordViewHeader #/launchProcess= links', () => {
})
})
const readOnly = { ...table, insertPermission: false, editPermission: false, deletePermission: false,
capabilities: ['TABLE_QUERY', 'TABLE_GET'] } as unknown as QTableMetaData
function renderTable(tableMetaData: QTableMetaData, auditSource: AuditSource = null) {
return render(
<QueryClientProvider client={new QueryClient()}>
<RecordViewHeader tableMetaData={tableMetaData} record={{ tableName: 'person', values: { id: 5 }, recordLabel: 'Morgan Sample' }}
t1Fields={[]} viewMode="tabs" setViewMode={vi.fn()} hideActions={false} navigateFrom={{ path: '/app/person', label: 'Person' }}
auditSource={auditSource} />
</QueryClientProvider>
)
}
/** Labels of the desktop Actions menu's items, after opening it. */
async function openDesktopActions(user: ReturnType<typeof userEvent.setup>) {
await user.click(screen.getByRole('button', { name: 'Record actions menu' }))
return screen.getAllByRole('menuitem').map((item) => item.textContent)
}
describe('RecordViewHeader for a read-only user', () => {
it('offers no phone Actions trigger when there is nothing to do', () => {
const readOnly = { ...table, insertPermission: false, editPermission: false, deletePermission: false,
capabilities: ['TABLE_QUERY', 'TABLE_GET'] } as unknown as QTableMetaData
render(
<QueryClientProvider client={new QueryClient()}>
<RecordViewHeader tableMetaData={readOnly} record={{ tableName: 'person', values: { id: 5 }, recordLabel: 'Morgan Sample' }}
t1Fields={[]} viewMode="tabs" setViewMode={vi.fn()} hideActions={false} navigateFrom={{ path: '/app/person', label: 'Person' }} />
</QueryClientProvider>
)
beforeEach(() => vi.clearAllMocks())
it('still offers Developer Mode from the desktop Actions menu, with no write actions (#45)', async () => {
const user = userEvent.setup()
renderTable(readOnly)
for (const action of ['Edit', 'Copy', 'Delete']) {
expect(screen.queryByRole('button', { name: `${action} Person record` })).not.toBeInTheDocument()
}
const items = await openDesktopActions(user)
expect(items).toEqual(['Developer Mode'])
await user.click(screen.getByRole('menuitem', { name: 'Developer Mode' }))
expect(push).toHaveBeenCalledWith('/app/person/5/dev')
})
it('offers Developer Mode and Audit from the phone sheet', async () => {
const user = userEvent.setup()
renderTable(readOnly, 'table')
await user.click(screen.getByRole('button', { name: 'Record actions' }))
const sheet = screen.getByRole('dialog', { name: 'Record actions' })
expect(within(sheet).getByRole('button', { name: 'Developer Mode' })).toBeInTheDocument()
expect(within(sheet).getByRole('button', { name: 'Audit' })).toBeInTheDocument()
expect(within(sheet).queryByRole('button', { name: 'Edit Person' })).not.toBeInTheDocument()
expect(within(sheet).queryByRole('button', { name: 'Delete Person' })).not.toBeInTheDocument()
})
it('offers no Actions controls when a custom menu resolves to nothing', () => {
const empty = { ...readOnly, menus: [{ slot: 'VIEW_SCREEN_ACTIONS', label: 'Actions', items: [
{ itemType: 'BUILT_IN', values: { option: 'EDIT' } },
] }] } as unknown as QTableMetaData
renderTable(empty)
expect(screen.queryByRole('button', { name: 'Record actions menu' })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Record actions' })).not.toBeInTheDocument()
})
})
describe('RecordViewHeader Actions menu for a table without processes or a custom menu (#45)', () => {
beforeEach(() => vi.clearAllMocks())
it('renders the default menu with New, Copy, Delete and Developer Mode', async () => {
const user = userEvent.setup()
renderTable(table)
expect(await openDesktopActions(user)).toEqual(['New', 'Copy', 'Edit', 'Delete', 'Developer Mode'])
await user.click(screen.getByRole('menuitem', { name: 'Developer Mode' }))
expect(push).toHaveBeenCalledWith('/app/person/5/dev')
})
it('adds Audit when the user can read audits', async () => {
const user = userEvent.setup()
renderTable(table, 'table')
const items = await openDesktopActions(user)
expect(items).toContain('Developer Mode')
expect(items).toContain('Audit')
})
it('does not duplicate Copy or Delete as standalone buttons beside the menu', async () => {
const user = userEvent.setup()
renderTable(table)
expect(screen.getByRole('button', { name: 'Edit Person record' })).toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Copy Person record' })).not.toBeInTheDocument()
expect(screen.queryByRole('button', { name: 'Delete Person record' })).not.toBeInTheDocument()
const items = await openDesktopActions(user)
expect(items.filter((label) => label === 'Copy')).toHaveLength(1)
expect(items.filter((label) => label === 'Delete')).toHaveLength(1)
})
})
describe('RecordViewHeader layout', () => {
it('keeps secondary actions in the process menu without duplicate desktop buttons', async () => {
const user = userEvent.setup()
@@ -170,10 +243,10 @@ describe('RecordViewHeader layout', () => {
expect(push).toHaveBeenCalledWith('/app/person/5/copy')
})
it('keeps standalone secondary actions when there is no process menu', () => {
renderHeader()
expect(screen.getByRole('button', { name: 'Copy Person record' })).toBeInTheDocument()
expect(screen.getByRole('button', { name: 'Delete Person record' })).toBeInTheDocument()
it('keeps the table processes in the Actions menu alongside Developer Mode', async () => {
const user = userEvent.setup()
renderHeader([{ name: 'tagRecords', label: 'Tag Records', hasPermission: true } as QProcessMetaData])
expect(await openDesktopActions(user)).toEqual(['New', 'Copy', 'Edit', 'Delete', 'Tag Records', 'Developer Mode'])
})
it('lets the controls wrap onto their own row instead of squeezing the title', () => {
+9 -8
View File
@@ -242,9 +242,11 @@ export function RecordViewHeader({
const phoneActionEntries = hasCustomActionsMenu ? actionEntries : defaultPhoneMenuExtras(actionEntries)
const hasMetadataActions = (tableMetaData.menus ?? []).some((menu) =>
(menu.slot === 'VIEW_SCREEN_ACTIONS' || menu.slot === 'VIEW_SCREEN_ADDITIONAL') && hasMenuItems(resolveMenu(menu)))
// Keep the existing read-only security gate: a default Developer Mode entry alone does not
// expose an otherwise empty Actions control, while a table's explicit menu still can.
const hasMobileActions = canEdit || canInsert || canDelete || availableProcesses.length > 0 || hasMetadataActions
// Show the Actions controls whenever the resolved menu has something to offer. The default menu
// always resolves Developer Mode (and Audit when the user can read audits), so a table without
// processes or a custom menu, or a read-only table, still reaches them, as Material did (#45).
const hasRecordActions = hasMenuItems(actionEntries) || canEdit || canInsert || canDelete
|| availableProcesses.length > 0 || hasMetadataActions
/**
* Runs a resolved menu action from either the desktop dropdown or the phone sheet.
@@ -296,9 +298,8 @@ export function RecordViewHeader({
const desktopActions = !hideActions && (
<div className="hidden md:flex md:flex-wrap md:items-center md:gap-2" data-qqq-id="record-view-desktop-actions">
{tableMetaData.shareableTableMetaData && <ShareButton tableMetaData={tableMetaData} record={record} />}
{hasMobileActions && <RecordActions className="flex-wrap" tableMetaData={tableMetaData} record={record}
actionEntries={hasCustomActionsMenu || availableProcesses.length > 0 ? actionEntries : []}
resolveMenu={resolveMenu} onAction={onAction} />}
{hasRecordActions && <RecordActions className="flex-wrap" tableMetaData={tableMetaData} record={record}
actionEntries={actionEntries} resolveMenu={resolveMenu} onAction={onAction} />}
</div>
)
@@ -437,7 +438,7 @@ export function RecordViewHeader({
{/* Mobile: bottom-sheet trigger button — MED-17 */}
<div className="flex items-center gap-2 md:hidden">
{tableMetaData.shareableTableMetaData && <ShareButton tableMetaData={tableMetaData} record={record} />}
{hasMobileActions && <button
{hasRecordActions && <button
type="button"
ref={mobileActionsTrigger}
onClick={() => setMobileActionsOpen(true)}
@@ -461,7 +462,7 @@ export function RecordViewHeader({
</div>
{/* Mobile actions bottom-sheet — MED-17 */}
{mobileActionsOpen && hasMobileActions && (
{mobileActionsOpen && hasRecordActions && (
<div className="md:hidden" data-qqq-id="mobile-actions-sheet">
{/* Backdrop */}
<div
+2 -1
View File
@@ -912,6 +912,7 @@
"fixture": "persona viewer",
"scenarios": [
"no Edit, Copy or Delete",
"the Actions menu (or phone sheet) still offers Developer Mode, with no New, Edit, Copy or Delete (QRun-IO/qqq-frontend-next#45)",
"create and edit URLs show the permission message"
],
"negative": [
@@ -1195,7 +1196,7 @@
"The phone Actions sheet fits the screen, takes focus, keeps Tab and Shift+Tab inside, closes by Escape or a backdrop tap with focus back on its trigger, and runs Edit"
],
"negative": [
"A read-only user gets no phone Actions trigger (asserted in REC-046)"
"A read-only user's phone Actions sheet offers no write action (asserted in REC-046)"
],
"issues": [
"QRun-IO/qqq#708"
@@ -120,8 +120,14 @@ test.describe('Processes added to every screen, for a user whose metadata omits
await open(page, '/app/qryBin/2')
await expect(page.getByRole('heading', { level: 1, name: 'Nuts' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Delete Storage Bin record' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Record actions menu' })).toHaveCount(0)
// the default Actions menu still renders (Delete, Developer Mode, Audit), without the omitted process (#45)
await page.getByRole('button', { name: 'Record actions menu' }).click()
const recordItems = page.locator('[data-qqq-id="record-view-actions-menu"]').getByRole('menuitem')
await expect(recordItems.filter({ hasText: 'Delete' })).toHaveCount(1)
await expect(recordItems.filter({ hasText: 'Developer Mode' })).toHaveCount(1)
await expect(recordItems.filter({ hasText: 'Audit' })).toHaveCount(1)
await expect(recordItems.filter({ hasText: 'Tag Records' })).toHaveCount(0)
await page.keyboard.press('Escape')
await expect(page.getByRole('button', { name: 'Tag Records', exact: true })).toHaveCount(0)
await open(page, `/app/${TAG}?recordsParam=recordIds&recordIds=1&tableName=person`)
await expect(page.locator('[data-qqq-id="not-found-state"]', { hasText: TAG })).toBeVisible()
@@ -153,7 +159,12 @@ test.describe('Processes added to every screen, without process permission', ()
await open(page, '/app/qryBin/2')
await expect(page.getByRole('heading', { level: 1, name: 'Nuts' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Edit Storage Bin record' })).toBeVisible()
await expect(page.getByRole('button', { name: 'Record actions menu' })).toHaveCount(0)
await page.getByRole('button', { name: 'Record actions menu' }).click()
const recordItems = page.locator('[data-qqq-id="record-view-actions-menu"]').getByRole('menuitem')
await expect(recordItems.filter({ hasText: 'Developer Mode' })).toHaveCount(1)
await expect(recordItems.filter({ hasText: 'Audit' })).toHaveCount(1)
await expect(recordItems.filter({ hasText: 'Tag Records' })).toHaveCount(0)
await page.keyboard.press('Escape')
await expect(page.getByRole('button', { name: 'Tag Records', exact: true })).toHaveCount(0)
// the v1 route the UI uses refuses it, as does the unversioned one
+20 -3
View File
@@ -56,9 +56,26 @@ test.describe('read-only persona', () => {
for (const action of ['Edit', 'Copy', 'Delete']) {
await expect(page.getByRole('button', { name: `${action} Person record` })).toHaveCount(0)
}
await expect(page.getByRole('button', { name: 'Record actions menu' })).toHaveCount(0)
// Phone: no Actions trigger opening an empty sheet either
await expect(page.getByRole('button', { name: 'Record actions', exact: true })).toHaveCount(0)
// The Actions control remains for Developer Mode (#45), but offers no write action
const desktopMenu = page.getByRole('button', { name: 'Record actions menu', exact: true })
const sheetTrigger = page.getByRole('button', { name: 'Record actions', exact: true })
await expect(desktopMenu.or(sheetTrigger).first()).toBeVisible()
if (await sheetTrigger.count()) {
await sheetTrigger.click()
const sheet = page.getByRole('dialog', { name: 'Record actions' })
await expect(sheet.getByRole('button', { name: 'Developer Mode', exact: true })).toBeVisible()
for (const action of ['New', 'Edit Person', 'Copy Person', 'Delete Person']) {
await expect(sheet.getByRole('button', { name: action, exact: true })).toHaveCount(0)
}
} else {
await desktopMenu.click()
const menu = page.locator('[data-qqq-id="record-view-actions-menu"]')
await expect(menu.getByRole('menuitem', { name: 'Developer Mode', exact: true })).toBeVisible()
for (const action of ['New', 'Edit', 'Copy', 'Delete']) {
await expect(menu.getByRole('menuitem', { name: action, exact: true })).toHaveCount(0)
}
}
await page.keyboard.press('Escape')
await page.goto('/app/person/1/edit', { waitUntil: 'domcontentloaded' })
await expect(page.getByRole('alert').filter({ hasText: 'permission' })).toHaveText('You do not have permission to edit Person records.')
+1 -1
View File
@@ -149,7 +149,7 @@ export async function openForm(page: Page, path: string, heading: string) {
/**
* Starts a record action (Edit, Copy or Delete) whether the view shows it as a
* button, inside the Actions menu (tables with record processes), or in the phone
* button, inside the Actions menu, or in the phone
* action sheet.
*/
export async function recordAction(page: Page, action: 'Edit' | 'Copy' | 'Delete', tableLabel: string) {